三、Unity编辑器介绍
Unity是一个商业级的3d游戏引擎。一个引擎的专业程度其实并不是体现在它多么牛b 的次世代效果,说实话那些效果即便你会用也不敢用,因为没有哪个手机是次世代的。游戏引擎的专业程度体现在两个方面,一是编辑器的完善程度,二是基础设施的完善程度和优化程度(比如说异步资源加载,文件打包系统,场景管理效率,裁剪性能,渲染优化,性能分析等等)。毫无疑问Unity在这些方面都是非常优秀的。除了写代码,你可以使用自己习惯的编辑器外,其他的东西都可以在Unity提供的一个大一统的编辑器下完成。包括场景编辑,游戏调试,资源管理,动画编辑等等。甚至你还可以轻易的扩展编辑器的功能,让它提供诸如关卡编辑、怪物编辑、技能编辑等等功能。
这个就是Unity的编辑器了。最上面是一行菜单,菜单可以轻易的通过代码来扩展。
第一部分是场景物件浏览窗口,所有的场景物件都会在这里显示出来,如果运行了游戏,那么动态创建的物件也会在这里显示出来。你可以点击其中的一个物件来查看和修改它的属性。
第二部分是游戏运行窗口,可以查看游戏运行结果,游戏运行的时候也可以切换到scene分页查看当前场景,在场景视口中可以操作场景摄像机和场景物件。
第三部分是物件属性窗口,可以查看当前选定物件或者资源的属性。每个物件必然都有Transform属性,控制物件的大小、旋转、缩放,这里的属性修改可以直接反馈到运行的游戏中,但是要注意,运行时的属性修改不会保存,停止运行后属性会还原。
第四部分是资源窗口,它显示的是当前Assets目录下的实际文件夹和文件。这里需要注意两点,点击这里然后修改属性对应的是文件修改,不存在游戏运行与否的区别。移动文件尽量在Unity内部进行,这样材质和脚本等会被正确的关联,但是如果是在windows浏览器下修改的,那么很有可能这些关联就都丢失了,你就不得不重新绑定或者设置。
四、Unity的开发语言
Unity的开发语言是C#,当然如果你非常熟悉javascript或者非常不喜欢c#,你也可以选择使用js来进行开发。在我看来js除了个人语言习惯外并没有其他优势,为了提高运行效率,Unity中的js并不是纯正的js,而是经过修改的(貌似是微软干的),而且js是编译成dll之后才加载运行的,这意味着不可能像cocos2d-x中的js一样,当作纯脚本动态的更新并加载。
C#原本是微软的.Net官方语言,是windows独有的。而开源界的大神们开启了一个名叫Mono的项目,用于把C#移植到Linux等非windows平台。而Unity正是基于Mono项目,使得其具备非凡的跨平台移植能力。
Unity执行C#的原理是先编译成dll(无论是哪个平台都是dll,这个动态库并不是程序直接加载的那种动态库,而是由Mono库来加载执行的),然后加载执行。这意味着你不能更新代码的内容(细节会在后面讨论自动更新的时候再说),因为代码文件已经包含程序包内了,但是在不更新代码内容的前提下,一个预制(Prefab,后面会介绍)中绑定哪个脚本以及脚本开放的参数配置都是可以随意更新的。
五、Unity的基本元素
Unity场景中的所有物件都是GameObject。GameObject是一个物件(如一个箱子,或者一个怪物),同时它也是一个节点,比如你可以创建一个空的GameObject然后把所有的怪物都挂接在这个节点下。场景物件窗口可以直接看到所有物件的层级关系。
每个GameObject都可以添加任意的Component,组件是Unity中另一个核心的基本要素。可以说万事万物皆组件,Unity中写代码的过程就是创建脚本作为组件挂接到GameObject上面来操作其属性的过程。你只能创建一个GameObject而不能通过继承来扩展它,但是你可以通过组件来任意的扩展它的功能,这就是基于组件的编程模式。习惯之后就会发现它的灵活和强大之处。
GameObject有一个必备的组件就是Transform,它描述了物件的旋转、偏移、缩放,在属性窗口中显示的都是相对于父节点的数值,如果你在代码中直接设置transform.position,Unity会自动把这个绝对坐标修改为相对于父节点的坐标。
其他一些常用的系统组件介绍:
Mesh Render、Skin Mesh Render模型都会有这个组件,负责模型的显示。可以说只要能够显示的东西,无论是复杂的人物模型,还是简单的几何体,或者纯粹是一个图片,都会有Mesh Render这个组件。
Material组件,材质跟Shader关联在一起,Mesh Render中选择依赖的材质,材质中选择使用的Shader,shader中选择对应的纹理。这几样东西配合起来就组成了一个可渲染的物体。
Collider组件,碰撞盒(Box Collider),模型碰撞体(Mesh Collider)等都是属于这个组件。要想发生碰撞必然要设置这个组件。
RigidBody组件,刚体组件,控制一个物体的质量、阻力等等。要想发生碰撞,碰撞的双方一定要有一个物体是具备刚体组件的。
Charactor Controller组件,这个比较特别,它与刚体组件等价,用于模拟rpg或者fps中的主角的物理运动效果,可以发生碰撞,但是不受真实物理影响。
Animator组件,控制人物动画播放。
Unity中最常用的系统组件就这些,剩下的还有一些是特殊物体特有的,比如摄像机控制、灯光、地形等等。
六、Unity脚本开发之函数
Unity很聪明的选择了C#来作为脚本,可以在保证效率的基础上降低了开发门槛。 C#具备三个非常优秀的品质,垃圾收集、完善的基础库和反射机制,这些是c++很难具备的(不是说c++就不具备,只不过实现并推广基于c++的垃圾收集和反射并不是一件多么容易的事情,像boost十多年了也没有真正普及)。
所有的脚本都是继承自MonoBehaviour,而MonoBehaviour则继承自Component,它就是一个组件。你可以随意创建脚本,然后把它挂接到GameObject,然后脚本就会被执行。MonoBehaviour提供了很多基础的功能,熟悉这些功能后Unity的脚本开发就不在话下了。
Awake Start Destroy OnEnable OnDisable Update OnGUI,这几个是最常用的函数,你只要在脚本中创建这么一个函数,那它就会在合适的时机调用。 调用的方式是基于反射的,并不是基于虚函数。所以函数是否是public,返回值是什么都无关紧要。但是要注意,如果是自己写的基于虚函数的override的话,就要在基类里面声明函数为virtual,子类里面声明为override,如果忘记写,编译器不会报错,但是执行不到正确的代码。
Awake是在刚绑定好脚本的时候(多数情况下就是GameObject被实例化好的时候)调用,这个时候所有的开放给编辑器配置参数都会初始化好,是脚本最先被执行的代码。
Start是物体在第一帧被渲染的时候调用,有时候你会实例化一个物体,然后再在代码中设置一些参数,而依赖于这种操作的代码都可以放到Start里面。
Update是每一帧都会被调用的函数。与之类似的还有FixedUpdate,这个函数是固定间隔调用,时间快慢、帧率快慢、是否在后台都没有关系。基本上可以这么认为除了物理模拟等特殊情况,否则能不用FixedUpdate就不用,能不写Update就不要再脚本中保留这个函数。
OnGUI是绘制Unity的UI的时候调用的,一般这个函数没什么用。除了测试的时候方便些(如随意两行代码写一个button,然后点击后触发一个测试的效果),无论从便捷性还是效率上都被那些GUI插件甩了几条街。
这里在额外提两个函数OnCollisionEnter和OnTriggerEnter,这两个是碰撞检测相关的。如果物体上有碰撞盒那么就会调用该物体绑定脚本中的这个函数。如果Collider勾选了IsTrigger选项,那么碰撞和不会发生碰撞位移,纯粹是作为触发器,调用的是Trigger系列的函数。否则调用的是Collision系列的函数。 每个系列都有Enter Stay Exist三个函数分别对应物体刚发生碰撞,持续碰撞中,物体分离的情况。
七、Unity脚本开发之属性与消息传递
每个组件(其实就是脚本内部),都有一些列属性可以方便的获取到我们想要的对象。属性内有gameObject、transform、rigidbody、collider、audio、render等属性。最常用的而且也是必然有的是gameObject和transform,它就是这个脚本所挂接的物体。如果这个物体上面同时还有刚体或者碰撞盒或者添加了声音组件,那么直接rigidbody等系统默认提供好的属性就可以用了。当然,你也可以手工在代码中使用GetComponent<要查找的类名>()来获取组件。
gameObject中有transform和rigidbody等属性,transform和rigidbody中同样有gameObject属性,这些引用关系可以方便的通过任意对象查找到你需要的东西。
你可以直接使用GameObject.Find("对象名字")来查找一个物体,其中对象名字可以包含层级路径。gameObject也可以使用GetComponent或者GetComponentInChildren来在当前或者子节点中获取对应的脚本。相关的函数非常丰富,你可以根据对象类型或者是Tag标签名等等来获取一个或者多个物体。
脚本内部可以使用Invoke("函数名", 延迟时间)来异步调用一个当前脚本内部的函数,或者使用SendMessage("函数名",参数)来调用一个函数,SendMessage非常方便的一点是它可以由gameObject来调用,只要你获得了一个物体,就可以使用SendMessage来调用一个函数,只要这个物体绑定的脚本中有一个同名函数,那么就会被调用。当然灵活也是有代价的,无论是Invoke还是SendMessage都比实际函数调用或者是委托(delegate)要低效很多,所以不要滥用(比如在Update中使用)。
熟悉了这几个知识点,那么你就可以写出符合Unity风格的代码了。剩下的都是对基础数学、算法、图形学、设计模式的理解了,这些知识是与引擎无关的,你拿Ogre能写出来,Unity也可以轻松的实现。
(第二部分在这里告一段落,它描述了Unity开发到底是怎么回事,虽然基础,但是是经过一段时间的代码积累才总结出来的,作为入门,它更加直面本质。我一直以为,会学习的人和不会学习的人差别就在总结和对本质的把握上面。会学习的人可以把握事物的本质,能够总结出这些东西究竟有哪几个知识点,原理是什么,原因是什么;而不会学习的人只会照猫画虎,能实现功能但是不得要领,知其然而不知道其所以然)
漫话Unity(二)