关于Unity脚本生命周期中部分基础且重要的函数的执行机制的实践了解
发表于2016-12-12
Unity脚本组件有一些“内置”事件函数,如 Reset、Awake、Start等,并且这些函数还有一定的执行顺序,即“脚本的生命周期”。这些函数既基础但又重要,了解这些函数的运行时机与机制,对于我们敲代码会有很好的帮助。
1.Reset()
当我们第一次将脚本组件挂载到游戏物体上时,该函数会执行一次;另外点击已经挂载到游戏物体上的脚本组件“右上角”的设置按钮,里面有个Reset选项,点击它,也可以执行一次。设置按钮位置截图如下:

测试代码如下:
1 2 3 4 5 6 7 | /// /// Reset函数测试 /// void Reset() { Debug.Log( "=============Reset函数===========" ); } |
测试结果如下截图:(按照上述2种方式操作,结果都如下)

该函数的作用:与其说是用来“初始化”一些变量参数的值,还不如说是“重置”它们的值。
值得注意的是,该函数运行模式是Editor,通俗地讲,就是我们在Unity中编辑游戏时才会执行而不是游戏运行时。
2.Awake()
已挂载测试脚本组件的游戏物体在场景中被加载实例化且游戏物体“第一次”被激活时才会执行一次,上述2个条件缺一不可。我们知道场景中的游戏物体,有2种加载方式,第1种是编辑游戏时,提前拖入游戏场景中的,它们是由Unity加载场景时,预加载实例化的游戏元素;第2种是在脚本中通过Instantiate函数实例化生成的。

测试脚本Life脚本中,关于Awake函数代码如下:
1 2 3 4 5 6 7 | /// /// Awake函数测试 /// void Awake() { Debug.Log( "=============Awake函数===========" ); } |
首先,将待测试的游戏物体拖入到测试场景中,并且取消游戏物体与脚本组件上激活框的“√”,然后点击游戏运行按钮。发现Console界面无Debug信息输出,说明Awake函数未执行。当第1次勾选上游戏物体的激活框时,发现Awake函数执行了,但是接着反复勾选与取消该激活框时,发现Awake函数不再继续执行···另外,特别值得注意的是,脚本Life的激活框勾选与否,与Awake函数的执行无关。
测试结果如下:

3.OnEnable()
测试代码如下:
1 2 3 4 5 6 7 | /// /// OnEnable函数测试 /// void OnEnable() { Debug.Log( "=============OnEnable函数===========" ); } |
情况1:挂载了测试脚本的游戏物体预先“激活”即处于Enabled的状态,但脚本组件未处于勾选“激活”的状态

当去勾选上脚本组件激活框时,会发现OnEnable函数会执行一次;如果接着反复进行取消和勾选上操作时,会发现OnEnable函数会重复被执行。

总结1:在游戏物体处于激活状态时,每次重新激活脚本组件,OnEnable函数都会执行;
情况2:挂载了测试脚本的游戏物体预先“未激活”即处于Disabled的状态,但脚本组件处于“勾选上”的状态

当勾选上游戏物体激活框时,发现OnEnable函数会执行一次;接着反复进行取消与勾选的操作时,OnEnable函数都会执行。
总结2:在脚本组件处于激活状态时,每次重新激活游戏物体,OnEnable函数都会执行;

4.Start()
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /// /// OnEnable函数测试 /// void OnEnable() { Debug.Log( "=============OnEnable函数===========" ); } /// /// Start函数测试 /// void Start () { Debug.Log( "=============Start函数===========" ); } |
情况1:挂载了测试脚本的游戏物体预先“激活”即处于Enabled的状态,但脚本组件未处于勾选“激活”的状态

首先确保游戏物体在场景中处于“激活的”状态;然后,“第一次”勾选上Life脚本组件激活框时,Start函数会在OnEnable函数执行之后,紧跟着执行。
运行结果如图:

但是紧接着反复取消与勾选激活框时,只发现OnEnable函数会执行,但Start函数不会再执行了。运行结果如图:

总结1::在游戏物体Enabled状态下,脚本组件第一次被Enable时,Start函数才会执行一次。
情况2:挂载了测试脚本的游戏物体预先“未激活”即处于Disabled的状态,但脚本组件处于“勾选上”的状态

点击游戏运行按钮时,发现无Debug信息打印出,说明OnEnable与Start函数都没有执行;当此时第一次勾选上游戏物体激活框时,Start函数会在OnEnable函数执行之后,紧跟着执行。运行结果如下图

接着反复进行取消勾选与勾选上游戏物体激活框时,会发现Start函数不再执行,而OnEnable函数会

总结2:在脚本组件处于激活即Enabled状态下时,游戏物体第一次被激活即Enable时,Start函数才会被执行一次
小结一下:游戏运行时,脚本的上述3个函数的执行顺序依次是:Awake→OnEnable→Start。
5.FixedUpdate()/Update()/LateUpdate()
打开Edit→Project Settings→Time的面板,截图如下:

其中Fixed Timestep 中文名固定时间步,它的值表示多长时间进行一次物理计算。常见的物理计算有碰撞检测与刚体的移动等,而这些物理计算要求必须是稳定、一致的,应当尽可能的避免硬件配置与帧速率(FPS)的不同所带来的影响。帧速率(FPS)指每秒画面刷新的次数,一般来说FPS>=30,玩家才不会感觉到画面卡顿,帧速率受到设备性能影响比较大。另外,FixedTimestep的值越小,就表示进行物理计算的频率越高,物理模拟也就越精确,但是CPU的负担也就越大。
运行结果截图如下:

接着修改FixedTimestep的值,FixedTimeStep = 1,再次运行结果截图如下:


会发现FixedUpdate函数执行的频率明显降低。
另外,从上面的执行结果,可以进一步分析出以下结论:
(1)3个函数的执行顺序是FixedUpdate→Update→LateUpdate
(2)Update与LateUpdate函数执行频率相同,即Update执行一次,LateUpdate紧随着也执行一次。
(3)FixedUpdate的运行相对独立于Update/LateUpdate。
FixedUpdate | 主要用来处理物理模拟的相关运算,如碰撞检测、刚体运动等 |
Update | 除上述外,那些需要每帧处理的逻辑运算可以放在该函数执行,在实际项目中,一般不建议经常使用该函数,因为挺好性能的 |
LateUpdate | 如果把第三人称的角色的转向与移动运算放在Update函数中进行时,那么一般就会把跟随角色移动的相机的移动与旋转运算放在LateUpdate函数中进行。这样做,可以确保角色完成移动后,才去移动它的“跟随”相机 |
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /// /// FixedUpdate函数测试 /// void FixedUpdate() { Debug.Log( "============= FixedUpdate函数测试 ===========" ); } /// /// Update函数测试 /// void Update() { Debug.Log( "============= Update函数测试 ===========" ); } /// /// LateUpdate函数测试 /// void LateUpdate() { Debug.Log( "============= UpdateLateUpdate函数测试 ===========" ); } |
6.OnDisable()
测试代码如下:
1 2 3 4 5 6 7 | /// /// OnDisable函数测试 /// void OnDisable() { Debug.Log( "============= OnDisable函数测试 ===========" ); } |
情况1:挂载了测试脚本的游戏物体预先“激活”即处于Enabled的状态

点击运行游戏时,注意此时OnDisable函数并未执行,当先勾选上再取消时,发现OnDisable函数执行了一次;反复上述先勾后消操作时,OnDisable函数都会执行。
总结1:在游戏物体处于“激活”状态时,每次重新取消勾选脚本组件激活框时,OnDisable函数都会执行。
情况2:挂载了测试脚本的游戏物体预先“未激活”即处于Disabled的状态,但脚本组件处于“勾选上”的状态

点击运行游戏时,注意此时OnDisable函数并未执行,当先勾选上再取消时,发现OnDisable函数执行了一次;反复上述先勾后消操作时,OnDisable函数都会执行。
总结2:在脚本组件处于“激活”状态时,每次重新取消勾选游戏物体激活框时,OnDisable函数都会执行。
情况3:在游戏物体与脚本组件都处于“激活”状态时

总结3:在游戏物体与脚本组件都处于“激活”状态时,停止游戏运行时,OnDisable函数也会执行一次。
7.OnDestroy()
测试代码如下:
1 2 3 4 5 6 7 | /// /// OnDestroy函数测试 /// void OnDestroy() { Debug.Log( "=========OnDestroy函数测试========" ); } |
一般会在当前运行的场景中把挂在脚本的游戏物体销毁时(为了测试,直接在游戏运行时,把场景中的游戏物体Delete掉),又或者在关闭销毁当前场景时会调用该函数(为了测试,关闭游戏运行按钮来模拟)。
这里区分以下4种情况:
情况1:游戏物体与挂载的脚本组件都处于Enable状态

通过上述2种方式测试,发现都会执行OnDestroy函数

情况2:游戏物体Enable状态,而脚本组件Disable状态

通过上述2种方式测试,发现都会执行OnDestroy函数

情况3:游戏物体Disable状态,而脚本组件Enable状态

通过上述2种方式测试,发现都不会执行OnDestroy函数

情况4:游戏物体Disable状态,而脚本组件Disable状态

通过上述2种方式测试,发现都不会执行OnDestroy函数

特别值得注意的是,在3和4情况下,游戏运行过程中,只要勾选上游戏物体的“激活框”一次(特别提醒:接着即使你再次取消游戏物体激活框上的勾选),再通过上述2种方式操作,都会发现OnDestroy函数会执行。
总结:只要场景中的游戏物体被Enable过一次,不管脚本组件有木有Enable,游戏物体在销毁时都会执行OnDestroy函数。
小结:上面讲述的几个函数,是比较常用的函数,它们可以组成Unity脚本的一个生命周期。我这里只是根据自己本人的亲身测试,概括了一下,什么时候会触发上述这些函数的执行。如果读者,想要在某个函数中处理一些逻辑代码,发现若没有被执行,那么可以考虑一下,是不是没满足触发执行的条件?另外,具体关于Unity脚本生命周期的官方文档,https://docs.unity3d.com/Manual/ExecutionOrder.html