赞
踩
这篇贴子的内容主要信息来源是Unity的官方文档Version2019.2:https://docs.unity3d.com/Manual/ExecutionOrder.html
也会根据这些年自己的开发经验,谈一些自己的看法和小技巧。
Unity的内置事件方法很多,最基础的也是最常用一些,列举如下:
方法名(按单次执行顺序排列) | 方法说明 |
Awake | 在实例化时首先执行的方法,整个生命周期中只执行一次 |
OnEnable | 每次进入激活状态都会执行,对应OnDisable |
Start | 在实例化以后的下一次Update之前调用,整个生命周期只执行一次 |
FixedUpdate | 物理更新方法,执行频率可在Project Setting->Time->Fixed Timestep进行设置 |
OnTriggerXXX | 触发器检测事件 |
OnCollisionXXX | 碰撞器检测事件 |
OnMouseXXX | 鼠标检测事件 |
Update | 帧更新方法 |
LateUpdate | 在Update之后执行的更新方法 |
OnGUI | GUI渲染逻辑执行方法 |
OnApplicationQuit | 程序退出时的执行方法 |
OnDisable | 每次设置为不激活状态时执行,对应OnDisable |
OnDestroy | 实例被销毁时的执行方法 |
首先简单说明一下:
构造方法: 这里容易被忽视,构造方法是在实例化时执行的,所以在顺序上一定优先于其他的任何方法。
Awake: 当脚本被实例化时首先执行的方法,如果依附的GameObject处于未激活状态,则Awake会在被激活时首先执行,该方法不依赖于enable属性。该方法在组件的生命周期里只会执行一次。
OnEnable: 设置脚本实例的enable属性未true时,该方法被首先调用。在MonoBehaviour派生的类被实例化时、场景被加载时,带有该组件的GameObject被创建时,这个方法都可能被调用,我这里是说的是可能,因为这依赖于生成的实例的enable属性是否为true。严格来说,OnEnable不应该属于初始化方法,因为每次将组件的enable属性设置为true时,该方法都会被调用一次。
Main:这个方法在官方流程里没有,但它的确可以正常的在OnEnable之后自动调用,感兴趣的可以试一下。
Reset: 该方法的在组件被附加到物体上和调用Reset命令时执行,在编辑器的Inspector窗口的每一个组件的右上角的齿轮,点开以后可以执行Reset命令。
Start: Start方法在下一次帧刷新之前被调用,需要强调的是,如果实例是在运行中生成的,不同于立即执行的Awake方法,Start方法是在当前帧的Update方法之后,LateUpdate方法执行之前调用的。这一点会在后面的测试中验证。
FixedUpdate: 一般来说,FixedUpdate方法的执行要比Update方法频繁。如果当前帧刷新的很慢,可以在一帧之中被调用好几次,如果当前的帧率的很快,也可能一次都不会被调用。该方法会在所有的物理运算和更新之前被调用。当你在FixedUpdate方法内部应用运动计算的时候,不需要与Time.delaTime进行乘法运算,因为FixedUpdate的调用周期是稳定的,可以在Timer中进行设置,是不依赖于当前帧速率的。但是,要知道在极端情况下,比如主线程被阻塞的时候,FixedUpdate因为也是在主线程中的,所以这种情况下就不能稳定调用了。
Update: 每帧调用一次;这也是帧更新中最常用的方法。
LateUpadate: 每帧一次,从名字也能看出来,是在Update之后执行的方法,最典型的应用是第三人称相机。在Update中执行角色的移动和旋转,在LateUpdate中执行摄像机的跟随,这样可以保证角色的移动和旋转在摄像机跟随之前就已经完成了。根据官方的流程图可以看出 LateUpdate并不是紧跟在Update之后执行的,在以前的工作过程中,还偶然发现了一个小秘密。
有三个脚本,Test1:
- public class Test1 : MonoBehaviour
- {
- private void Awake()
- {
- Debug.Log("Test1 Awake");
- }
-
- private void OnEnable()
- {
- Debug.Log("Test1 OnEnable");
- }
-
- private void Start()
- {
- Debug.Log("Test1 Start");
- gameObject.AddComponent<Test2>();
- }
-
- private void Update()
- {
- if (!gameObject.GetComponent<Test3>())
- {
- gameObject.AddComponent<Test3>();
- }
- Debug.Log("Test1 Update");
- }
-
- private void LateUpdate()
- {
- Debug.Log("Test1 LateUpdate");
- }
-
- }
Test2(Test3相同):
- public class Test2 : MonoBehaviour
- {
- private void OnEnable()
- {
- Debug.Log("Test2 OnEnable");
- }
-
- private void Awake()
- {
- Debug.Log("Test2 Awake");
- }
-
- // Start is called before the first frame update
- void Start()
- {
- Debug.Log("Test2 Start");
- }
-
- void Update()
- {
- Debug.Log("Test2 Update");
- }
-
- private void LateUpdate()
- {
- Debug.Log("Test2 LateUpdate");
- }
-
- }
场景种只在摄像机上挂了Test1脚本。执行结果如下:
我们重点看Test3,Test3在第一帧的Update中被附加到gameObject,Awake立即执行,OnEnable紧跟Awake执行,但是Test3的Start却是在第一帧Update结束以后,LateUpdate开始之前执行的!!!所以说,在游戏运行过程中生成的组件,其Start还是在当前帧完成的,只不过是在所有Update之后,LateUpdate之前。还有一点需要注意,Test3在第一帧中没有执行Update,因为Test3的初始化是在Update中完成的,所以Test3的Update不可能再被调用,但是在系统执行LateUpdate时,此时的Test3已经完成了注册,所以Test3的LateUpdate被正常调用了!!!
到这里有没有对Start的执行感到困惑呢?Start就行是什么时候执行的???
我们把上面的试验中的Test1稍作改动:
- public class Test1 : MonoBehaviour
- {
- private void Awake()
- {
- Debug.Log("Test1 Awake");
- }
-
- private void OnEnable()
- {
- Debug.Log("Test1 OnEnable");
- }
-
- private void Start()
- {
- Debug.Log("Test1 Start");
- }
-
- private void Update()
- {
- if (!gameObject.GetComponent<Test3>())
- {
- gameObject.AddComponent<Test3>();
- }
- Debug.Log("Test1 Update");
- }
-
- private void LateUpdate()
- {
- gameObject.AddComponent<Test2>();
- Debug.Log("Test1 LateUpdate");
- }
- }
唯一的修改就是将Test2添加的时机移动到了Test1的LateUpdate内。在运行前选中Pause,我们只看第一帧的数据,执行结果如下:
Test2的Start方法在LateUpdate之后被执行了!!!到这里,你是不是能想到什么?
//-----------------------------------------------------------------
//等我哪天有确凿证据证明我的想法了,再来补充
//-----------------------------------------------------------------
OnApplicationPause: 检测到Pause请求,会在当前帧执行完以后调用该方法。在调用OnApplicationPause之后,将创建额外的一帧,以允许游戏显示表示暂停状态的图形。
官方给出的流程图中出现了OnStateMachineEnter/Exit,但是这两个方法实在StateMachineBehaviour中定义的,不属于MonoBehaviour,所以在这里不做讨论。但是如果单独的只说OnAnimatorMove和OnAnimatorIK又好像没什么意义。这里动画作为相对独立的一部分,以后有机会再做详细讨论,这里我只明确一下,动画更新是在Update之后,LateUpdate之前调用的。
渲染作为我个人的主攻方向之一,可以讲的东西非常多,与渲染有关的事件方法的调用顺序从图中看就很明确了。所以我觉得详细的用法和经验,也作为之后的单独的一个贴子去讨论。我们在这里只明确,渲染相关的方法调用是在LateUpdate之后依次执行的。
协程是可以在用户设置的延迟指令结束以后被延迟执行的方法。
Unity中协程的本质是迭代器(IEnumerator),开启一个协程后,在yield return之前的的代码会立即执行,遇到yield return则协程被挂起,等待之后的调用,流程图中的调用就是协程继续执行的条件判断触发的过程,大部分条件判断是在Update之后,动画更新之前。如果判断条件成立,则执行后面的代码。
很多时候我们只是需要等待一帧,这种情况下使用
yield return null;
要比下面的这两种方式节省一次实例化的开销
- yield return 0;
- yield return new WaitForSeconds(Time.deltaTime);
协程作为Unity开发很重要的一部分,这里只简单一说,后面有机会还是要单独开一篇的。
OnDestroy 的调用是在实例存在的最后一帧被调用的,实例的销毁可能时由于Object.Destroy()方法的调用或者场景的销毁。顺便一提,实例真正的被销毁是在OnDestroy执行之后进行的,OnDestroy()的执行又是在,而不是Destroy()方法调用时立即执行,所以也可以说,Destroy方法有一定的延迟。如果是使用DestroyImmediate()方法要求立即销毁该对象,则OnDestroy也会被立即执行,而不是到当前执行的事件(如Update,LateUpdate)结束以后,该方法的调用机制类似于Start方法。
OnApplicationQuit 每一个实现的该方法在程序退出前都会被调用,编辑器中调用Application.Quit()方法是无效的。
OnDisable 同OnEnable一样,在设置更改enable值和所依附物体的active属性,都会执行该方法。强调一下,在被销毁时,也会执行该方法,如果是同Destroy方法销毁对象,OnDisable方法会被立即执行,而OnDestory方法要在Update之后。
MonoBehaviour中的方法并不止这些,但这些都是最常用的一些。像OnApplicationFocus(bool hasFocus)这样的方法还有很多,具体的执行方式,还是自己多去探索。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。