Component 组件脚本及其基本生命周期
3.1 组件(Component)
组件是 Unity 中最核心的概念,也是一切编程的基础。没有组件,就无法进行 Unity 编程。
打开一个新的 Unity 工程,在 Project 面板中右键可以直接创建一个 C# 脚本。默认脚本的内容如下:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
默认的脚本继承自 MonoBehaviour 类,这是自定义脚本组件通常继承的类,即我们自己编写的脚本的父类。而 Unity 内部组件,如相机等,继承自 MonoBehaviour 的父类 Behavior 或者更上层的父类 Component。
Unity 为何要分成三个级别继承呢?从 Component 到 Behavior 只是增加了一个是否可以 enable 的属性,用于区分有些组件可以禁用,而有些组件不可以。从 Behavior 到 MonoBehaviour,则是专门为 Unity 程序员准备的,因为它增加了很多响应消息,包括上述代码中看到的 Start、Update 以及后面会提到的 LateUpdate、FixedUpdate 等消息。这些消息是为了让程序员可以方便地控制和响应组件,而 Unity 内置组件不需要这些消息,因为它们内部知道何时需要进行启动、更新等操作。
我们尝试参考 MonoBehaviour 的文档,将常见的消息响应全部打印到控制台上,代码如下:
using UnityEngine;
using System.Collections;
using Assets.AndrewBox.Util;
public class TestComponenets : MonoBehaviour {
void Awake() {
Debuger.LogAtFrame("Awake");
}
void Start () {
Debuger.LogAtFrame("Start");
}
// Update is called once per frame
void Update () {
// Debuger.LogAtFrame("Update");
}
void LateUpdate() {
// Debuger.LogAtFrame("LateUpdate");
}
void FixedUpdate() {
// Debuger.LogAtFrame("FixedUpdate");
}
void OnEnable() {
Debuger.LogAtFrame("OnEnable");
}
void OnDisable() {
Debuger.LogAtFrame("OnDisable");
}
void OnDestroy() {
Debuger.LogAtFrame("OnDestroy");
}
}
附加的 Debuger 类用于打印消息,它在显示消息的同时,记录了当前画面运行的帧数,便于我们观察函数调用的次序和时机:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Assets.AndrewBox.Util {
public static class Debuger {
public static void LogAtFrame(string infor) {
Debug.Log("[" + Time.frameCount + "]" + infor);
}
}
}
准备好代码之后,在场景中新建一个 Cube(实际上任意 GameObject 都可以),将 TestComponenets 脚本拖放到该 Cube 上。然后启动运行程序,并在 Cube 的 TestComponenets 组件上,将勾选状态关闭再打开,此时可以在控制台看到输出内容。之后停止运行,去掉上述代码中的注释,使几个 Update 方法暴露出来,再次运行程序查看结果。
最终,我们可以得出如下结论:
- Awake 方法:当
GameObject被启用时,该方法立刻执行。从中文字面意思理解,组件已经“苏醒”,但还未执行,只是做好了准备。该方法只执行一次。 - OnEnable 方法:当组件被启用时(前提是
GameObject已启用),该方法立刻执行,多次启用时会反复执行。 - OnDisable 方法:与
OnEnable方法对应,当组件被禁用时,该方法立刻执行,多次禁用时会反复执行。 - Start 方法:组件被启用后的下一帧才会执行,且只执行一次。特别注意,这里是下一帧,如果不注意,在资源加载方面可能会出现问题。
- OnDestroy 方法:当组件被销毁时执行。
- Update 方法:每帧执行一次,每秒刷新次数取决于硬件图像的刷新速度。
- LateUpdate 方法:每帧执行一次,在
Update方法之后执行,一般用于绘制到屏幕的最后处理(如果没有此特殊需求,使用Update方法即可)。 - FixedUpdate 方法:默认每隔 0.02 秒(具体时间可以设置)执行一次,与图像刷新率无关,用于物理逻辑计算。
正常情况下,执行顺序如下:
也就是说,在 Awake、OnEnable、Start 方法执行之后,开始几种 Update 循环。
一般来说,OnEnable 方法用于处理开启和关闭组件时的开关量转换,而组件的初始化可以写在 Awake 和 Start 方法中。
由于 Awake 方法在加载和启用 GameObject 后立刻执行,因此,如果本组件跟随 GameObject 加载后,应该立刻初始化本组件的公共成员(如果这些成员需要被其他代码访问)。因为如果在 Start 方法中初始化这些成员,需要等待一帧,而在这一帧过程中,很可能已经发生了对这些公共成员的访问,此时成员尚未初始化。所以应该避免这种情况的发生。
我们暂时将这种初始化方式称为二阶段初始化,以便更好地记忆。在后续的章节中会有更多体现。