熟悉 C# 开发的朋友, 在刚进入 Unity 开发时, 不可避免的会有一些迷惑, 例如不清楚 Unity 自己的思想, 如何设计与架构一个应用程序之类的. 本篇文章简要的介绍一下 Unity 的基础编程思想.
独立
Unity 很少使用 C# 的标准库, 例如 C# 的网络, 事件驱动, 对象模型, 这些概念在 Unity 中几乎不会被用到. 甚至于, 连标准的 System 命名空间都很少被引用.
Unity 自己实现了一套较为完善的标准库, 以适用于游戏开发. 例如最简单的 "随机数", 在 Unity 中, UnityEngine.Random
类实现了随机数生成. 并且它是一个静态类, 不需要我们创建随机数对象, 即可使用.
Unity 用到的, 最多是 C# 中的集合, LINQ 查询这些. 甚至于, Unity 中的 UI 事件处理器, 都不是一个标准的 C# EventHandler
. 完完全全算得上邪教.
命名
刚开始使用 Unity 的你, 肯定会疑惑, 为什么 Unity 的脚本中, 用于获取 Transform
组件的属性, 叫做 transform
, 而不是 Transform
. 一个公开的属性, 却不使用大驼峰命名法, 而是使用小驼峰?
事实上, Unity 压根不用 C# 的命名规范, 只有一部分与 C# 的命名规范统一. 例如, 它的类型还是大驼峰的, 它的接口也是以 I
开头的大驼峰命名. 不过单单属性命名与 C# 不统一这件事, 就已经能让大多数注重优雅的 C# 程序员火冒三丈了.
对象
刚刚也提到了, Unity 自己单独实现了一整套标准库, 只有少部分是使用 C# 的东西. 事实上, 在 Unity 中, 所有 Unity 可引用的对象都是由 UnityEngine.Object
派生而来的. 例如游戏对象 UnityEngine.GameObject
, 还有诸如 Camea
, RigidBody
这些各种各样的组件.
Unity 的对象封装了很多静态方法, 例如克隆一个对象, 销毁一个对象, 在场景中查找某个类型的对象. 由于我们在 Unity 中编辑的 C# 代码大多是一个继承了 UnityEngine.MonoBehavior
的脚本, 而它又间接继承 UnityEngine.Object
, 所以我们在代码中访问这些方法的时候, 就不需要写类名, 直接调用即可.
游戏对象与组件
在 C# 中, 实现功能的拓展是通过继承来实现的, 例如 Button
继承 Control
, 并在 Control
的基础上拓展出自己的功能, 但是 Unity 不是这样.
Unity 中, 每一个场景中的对象, 就单纯只是一个 GameObject
, 它自己本身没有任何功能. 如果要给一个对象添加一些功能, 就要为它挂载一些组件. 例如游戏中原始的一个 Sphere
物体, 它由 Transform
, MeshFilter
, MeshRenderer
, SphereCollider
这些组件构成, 他们分别负责承载 "物体的变换, 包括位置, 旋转, 缩放", "网格的存储", "网格的渲染", "碰撞相关功能".
不过, Unity 中的组件本身还是存在继承关系的. 例如 SphereCollider
和 BoxCollider
都是 Collider
的派生类.
只要理解了任何游戏中的物体都有一个最基本的游戏对象和提供功能的组件构成, 就可以理解大部分问题了. 根据这一点, 如果你想自己手动创建一个 Sphere
, 也可以先创建一个空对象, 然后按照 Sphere
的构成, 将所需要的组件逐个添加上去, 并正确的设置好属性, 就可以完美的复制出和自带的 Sphere
一模一样的物体了.
组件之间的依赖与互斥
组件也是有相互的关系的, 就像我们所用的 控制反转
中的服务一样, 我们将功能抽成一个个服务, 而服务之间存在依赖关系. Unity 的组件也是如此.
如果要使功能正常运行, 就需要同时添加组件所依赖的组件. 例如要实现物理效果, 我们需要添加一个 RigidBody
组件, 但是要使物体之间能够产生碰撞, 就需要再添加一个 Collider
. 如果单独添加 RigidBody
, 虽然物体会受重力影响, 但是因为不会产生碰撞, 所以会直接穿过其他物体.
至于组件的互斥, 有两种, 一种是完全不能共存的, 例如无法为一个物体同时添加 BoxCollider
和 BoxCollider2D
, 如果尝试添加, 编辑器中会提示 "组件冲突". 另一种则是, 可以同时添加, 但是会产生一些奇怪效果的. 例如 CharacterController
和 RigidBody
同时添加到物体中时, 就会引起移动相关的问题.
全局单例与静态类
Unity 中很少使用静态类, 除非你要用静态类承载一些工具方法. 但如果是一些逻辑相关的东西, 那它基本都是像普通的脚本一样是非静态的, 而想要全局访问, 在 Unity 中更多使用的是 "单例模式".
在平常的开发中, 我们大抵是很少用到单例的. 因为直接用静态就可以解决问题. 但是 Unity 中使用静态的话, 我们就没办法将它作为组件挂载到游戏对象上, 而且也不能通过 Unity 的编辑器对它的一些变量进行赋值, 不是很方便. 所以 Unity 单例用的更多.
需要注意的是, 平常编程中所使用的单例是没办法直接应用在 Unity 上的, Unity 的对象产生与销毁都与平常的开发有所不同, 需要做一点针对 Unity 的 "本地化" 才能正确使用.
管理类
Unity 会使用大量的 "管理类" 用来集中管理逻辑. 甚至于, 当你创建一个名字为 GameManager
的脚本时, 它的图标不是一个 C# 脚本图标, 而是一个特殊的齿轮图标. 由此可见 Unity 开发中有多重视这个.
相比较我们平常的桌面开发, 后端开发, 之所以 Unity 更常使用所谓的管理类, 是因为 Unity 游戏对象的通信是比较麻烦的, 而且一整个游戏的逻辑也更加复杂, 例如一场战斗的进行, 这时使用一个 "战斗管理" 的类来记录战斗相关的数据, 调用角色进行战斗, 要更加方便些.