3.3 Unity的脚本系统
Unity的脚本系统在开发过程中十分重要,哪怕是最简单的游戏项目都需要使用脚本对用户的操作进行反馈。脚本可以用于场景中几乎所有的事件触发、游戏对象的移动和玩家的交互。本节将介绍关于Unity脚本创建、使用和命名等的内容。
3.3.1 创建脚本
在Unity中,创建脚本的方式通常有两种。
第一种方式:在Project面板中单击右键,依次选择Create→C# Script命令,如图3-6所示。
图3-6 在Unity中创建C#脚本
第二种方式:在顶部菜单依次选择Assets→Create→C# Script命令,如图3-7所示。
图3-7 在Unity中创建C#脚本
除此之外,我们还可以在Inspector视图中通过Add Component方式直接创建C#脚本。
【示例3-7】创建新脚本
在Unity中,使用上述任意一种方法创建一个名为FirstTest的脚本文件。双击该脚本文件,默认会通过Visual Studio打开,如图3-8所示。
图3-8 Visual Studio编辑器界面
新创建的C#脚本如代码清单3-8所示。
代码清单3-8 新创建的C#脚本
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FirstTest : MonoBehaviour { // 初始化方法 void Start () { } // 在游戏的每一帧调用 void Update () { } }
在以上代码中,public class FirstTest : MonoBehaviour表示该脚本名为FirstTest,继承自MonoBehaviour。只有继承自Monobehaviour的脚本,才能够使用Unity提供的API。
脚本中默认提供了Start和Update两个方法。这两个方法都是Unity生命周期的一部分,为private类型。Unity生命周期相关的方法在运行时由Unity自己调用。
其中,Start方法会在脚本第一次激活的时候调用,而Update方法在每一帧画面中都会被调用。
3.3.2 事件函数
Unity中的脚本和一般计算机程序的运行方式略有不同。一般的计算机程序会在某个循环内持续执行,直到完成任务。而Unity会通过调用事件内部事先声明的函数,把控制权交给某个脚本。每当某个函数执行完自己的任务后,控制权就会交回给Unity。这些函数被称为事件函数。当Unity响应游戏中的事件时,事件函数会被调用。
Unity使用了特定的命名机制,以识别响应特定事件的函数。以下列出了Unity中最常见也是最重要的一些事件函数。
1. 更新事件函数
在Unity中创建脚本时,脚本中会默认添加更新事件函数。
游戏在某种程度上可以看作一个动画,其中的每一帧图像都是事先设置好的。在游戏中,在渲染每一帧画面之前都需要改变游戏对象的位置、状态和行为。而Update函数的作用正是执行此类任务。例如:
void Update() { float distance = speed * Time.deltaTime * Input.GetAxis("Horizontal"); transform.Translate(Vector3.right * speed); }
和帧的渲染类似,物理引擎也需要断断续续更新。FixedUpdate事件函数会在每次物理更新前调用。例如:
void FixedUpdate() { Vector3 force = transform.forward * driveForce * Input.GetAxis("Vertical"); rigidbody.AddForce(force); }
除了Update和FixedUpdate函数,还有一个LateUpdate函数,感兴趣的读者可以查询官方文档了解其作用。
2. 初始化事件函数
每个新创建的脚本中都有一个Start函数,其作用是在游戏的第一帧更新前被调用。
当某个游戏场景开启的时候,会调用Awake、OnEnable、OnLevelWasLoaded等函数。
3. 界面事件函数
Unity中有一个OnGUI系统用来渲染并响应GUI控件。不过在UGUI系统推出后,GUI系统的使用频度和作用相对小了。以下代码在屏幕中显示一个Game Over的标签:
void OnGUI() { GUI.Label(labelRect, "Game Over"); }
4. 物理事件函数
物理引擎通过特定的事件函数来判断碰撞、触发等事件,比如OnCollisonEnter、OnCollisonStay、OnCollisionExit分别在碰撞开始、持续与结束时被调用。如果碰撞体设置为触发器,则可以调用对应的OnTriggerEnter、OnTriggerStay、OnTriggerExit函数。例如:
void OnCollisionEnter(otherObj: Collision) { if (otherObj.tag == "Arrow") { ApplyDamage(10); } }
Unity脚本中有非常多的事件函数,它们的执行顺序是Unity预先确定的。其执行顺序可以参考图3-9所示。
图3-9 Unity中的脚本生命周期
【示例3-8】测试事件函数的执行顺序
创建一个名为LifeCyleTest的Script脚本,测试事件函数执行顺序的实现代码如代码清单3-9所示。
代码清单3-9 测试事件函数的执行顺序
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LifeCycleTest : MonoBehaviour { void Awake () { Debug.Log("Awake"); } void Start () { Debug.Log("Start"); } void FixedUpdate(){ Debug.Log("FixedUpdate"); } void Update () { Debug.Log("Update"); } void LateUpdate () { Debug.Log("LateUpdate"); } void OnDestroy () { Debug.Log("Destroy"); } }
接下来在场景中新建一个空白游戏物体,并将上述脚本挂载到该对象上。
在运行场景之前,确保选中Console窗口的Collapse选项,如图3-10所示。
图3-10 选中Collapse选项
运行场景,Console面板会输出各个生命周期函数的执行次数,如图3-11所示。
图3-11 Console面板输出的结果
场景运行结束后,可以清楚看到最先执行的是Awake函数,随后是Start函数,这两个方法都执行了一次;接下来调用的是FixedUpdate函数。Update和LateUpdate函数,这三个函数的执行频率一样,都是在每帧图像更新时执行一次;最后在结束场景运行时,调用Destroy函数。
这些事件函数只是Unity生命周期的一部分,其他部分会在后续章节详细讲解。
Unity Events不仅包含脚本的生命周期函数,还包含一些由系统自动调用的函数,也就是回调函数。限于篇幅,此处不详细讲解回调的概念,读者只需要明白,这些函数是由Unity在适当的时候自行调用。