这篇文章包含哪些内容
这篇文章从一个Empty的MonoBehaviour入手,首先讨论一下C#的修饰符internal,default,virtual,sealed
接着讨论一下MonoBehaviour,Component,Tranform,GameObject之间的关系 及脚本之间的如何互相关联
从一个空类说起
using UnityEngine; using System.Collections; public class EmptyClass : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
随便在工程中建立一个C#脚本,Unity就替我们生成了这个空类. 里面包含有两个方法Start,Update.
首先让我们抛开这两个方法的作用不谈,先来谈谈这两个函数的写法.
EmptyClass : MonoBehaviour 这个很简单,表示Empty继承自MonoBehaviour 。在Java或者AS中用extends这里用分号 这个无所谓了.
不过 void Start () {} 这行代码就很奇怪了. 前面没有任何的修饰符(priate,protected,public). 那这到底什么意思呢?
不加scope修饰符则为private(不建议这样使用)
google一下有篇文章给出了很好地解释
Should private members be declared explicitly as private in idiomatic C#?
http://www.4byte.cn/question/543979/should-private-members-be-declared-explicitly-as-private-in-idiomatic-c.html
文章说的很清楚,在C#中如果不加任何的scope修饰符则默认为private,不过几个Answer(我也赞同)不要使用这种方式,还是显示的加上private为好。
internal的作用域为一个工程
internal这个应该不太能用到,不过还是提一句吧. 也是在我无意中搜索到的,网上文章说的不是很清楚 所以我重新做了一个Demo,
正好也验证了defult的作用域
这张图就是整个workspace的结构,每个Solution就是一个个的工程,
而按图来说如果在LibSolution中定义了internal则MainSolution是访问不到的
LibA中定义了internal的变量及方法
在同一个Solution内,其子类和非子类对其的访问权限
在不同的Solution下对其的访问权限
virtual表示子类可以override,sealed不行,C#不写virtual则默认为sealed
这个很好理解,不过正好和Java反过来,Java是需要受到final掉才可以,如果有兴趣可以阅读这篇文章
http://stackoverflow.com/questions/1327544/what-is-the-equivalent-of-javas-final-in-c
Unity会自动调用Start,Update等方法即使他们为private
让我们再回头看Unity为我们创建的那个空类,里面只有两个方法 并且两个方法都没有写作用域,我们刚才也提到 没写作用域就是private的
那Unity为何能调用到这个函数呢?
http://stackoverflow.com/questions/24772681/c-sharp-when-do-i-need-override-when-dont-i-need-it
万能的stackoverflow 有人已经给出了解释 :单从语言的角度来说是不行的,除非你使用反射(反射可以访问private?),不过Unity并没有这么使用
我也不知道他们怎么做的...
不管怎样 就可以理解为Unity搞定了这个问题 :)
MonoBehaviour,Component,Tranform,GameObject
老实的说 搞定了上面的一坨东西 其实对于我们理解Unity没有任何的帮助...(万恶的刨根问底啊)
那所建立的脚本和场景上的对象(GameObject)到底是什么关系呢?
了解GameObject
网上的教程 里面都会出现GameObject,Transfomr等等 然后让你新建一个脚本往一个对象上一拽 ok 开始操作写代码
可以到底脚本是怎么和那个对象关联起来的呢?
其实 GameObject是一个纯粹的容器,一副骨架 什么都没有 甚至连显示都不行。其唯一的作用就是可以往上添加Component,
想成一个人的模型 那就是 添加了眼睛 你就看得见了,添加了嘴 你就能说话了。
同理想要碰撞就加BoxCollieder,想要声音就加AudioSource,即使我们用菜单创建出一个Cube它本身也是由
Transform+Cube+BoxCollider+MeshRender 这几个Component构成的.
我想你一定会发现,当你创建一个GameObject时候Unity已经为你自动添加了一个Component,那就是Transform。
从Inspector中也可以看出,每个GameObject都包含了一个Transform组件。
把GameObject连接起来的Transform
这个Component并不像他在Inspector中表现的那么简单 只负责x,y,z
Transform还有另外一个作用 就是连接GameObject, 比如说有两个Cube A B, 把B拖到A的下面 就变成了A得子
当改变A在世界坐标中的位置时候B也随之改变,这其中的功劳就是Transform。
在Unity中写的脚本就是Component
在我们进一步的研究Transform之前还是回过头来看一下在文章开头提到的那个空类
public class EmptyClass : MonoBehaviour
也就是说EmptyClass(我自己起的名字) 是继承自MonoBehaviour , 查看Assmbly Browser 会看到 MonoBehaviour是继承自Behaviour
而Behaviour呢 自然就是继承自Component喽。
也就是说 每个建立的脚本都是捆绑在GameObject上面的一个Component.
脚本之间如何互相关联
有了以上只是的铺垫 就可以进入这片文章的主题了....
还是由问题入手:
我在一个GameObject上面挂在两个Class的实例(注意是实例而不是类,挂载就相当于new出来一个对象)ClassA,ClassB
那我如何能让ClassA访问到ClassB呢?
Unity里面拖拽
这个也是Unity很牛逼的一个地方,只要在ClassA中建立一个public的变量类型是ClassB 同理在ClassB中建立一个public的变量类型为ClassA
在编辑器中直接互相拖拽一下....
突然发现,咦 没法拖拽啊... 恩 是的 因为Unity编辑器里面的拖拽绑定方式是GameObject级别的.在编辑器里面可以把两个GameObject通过这种方式
进行互相或者单方向的引用,但是GameObject内部的Component是不可以的.
GameObject内部的互相引用
当写的两个脚本在同一个GameObject内部时候想互相引用就需要用到gameObject这个变量了
注意在脚本内部可以访问到两个gameObject, 一个是大写的GameObject 一个是小写的gameObject 大写的GameObject相当于场景级的
也就是刚才想尝试直接在Unity里面拖拽时候操作一样,通过他提供的函数可以找到当前场景中所有的GameObject, 而小写的gameObject呢
其实就是指的是当前Component被捆绑上的GameObject
当确定同一个类型的Component只有一个时候及可以使用
ClassA AInstance = gameObject.GetComponent<ClassA> ();
ClassB BInstance = gameObject.GetComponent ("ClassB") as ClassB;
两种方式都行,如果有多个Component的话则需要使用gameObject.GetComponents() 然后再进一步的for循环查找等. 相关的可以看下官方的文档
这样也就实现了之前说的目的,在同一个GameObject内部 实现了脚本(Component)之间的互相引用。
两个互相连接的GameObject之间的内部脚本互相引用
把问题引申一步,还是那两个脚本ClassA,ClassB,不过这回不是绑在同一个GameObject上面 而是分辨绑定在两个GameObject
Parent(ClassA),Child(ClassB)
首先还是来尝试拖拽,虽然无法在Unity的编辑器中通过拖拽互相引用脚本(Componet),不过绑定GameObject是可以.
所以只需要建立两个public的变量 然后类型都是GameObject,在Unity里面互相拖拽引用,
最后在Start函数时候通过已经绑定好的gameObject调用其GetComponent方法即可
problem solved~
的确 这个方法是可行,不过有个更好的方法就是使用Transform.
刚才有提到Transform是一个很特殊的Component,其内部保留着GameObject之间的显示树结构.
所以就上面的例子来说 当要从Child访问到Parent 只需要在Child对应的脚本里面写
transform.parent.gameObject.GetComponent<ClassA>() 即可
返过来就相对麻烦一点,因为无法保证一个parent只有一个child,所以无法简单的使用
transform.child.gameObject这样访问, 但是Unity给我们提供了一个很方便的函数,那就是Find.(Find=FindChild,FindChild 也许即将废弃? API中并无该函数说明)
需要注意的是Find只能查找其Child,举个复杂点的例子
Parent->ChildA->ChildB->ChildC
当在Patent中想要找到ChildC中的一个Component时候 调用 transform.Find("ChildA/ChildB/ChildC").gameObject;
但是如果在ChildA中 同样需要找到ChildC的一个Component时候 需要调用 transform.FindChild ("ChildB/ChildC").gameObject;
总结
脚本之间需要互相引用的话,在不借助Unity编辑器帮忙的情况下 就需要首先通过Transform找到对应的GameObject节点,
在通过GameObject的GetComponent方法找到对应的脚本
That's All
Best
Eran