Unity基础开发之事件函数的执行顺序
文章目录
- 前言
- 一、Initializtion阶段
- Awake()
- OnEnable()
- 二、Editor阶段
- Reset()
- OnValidate()
- 三、第一次更新帧时
- Start()
- 四、帧循环阶段
- FixedUpdate()
- 动画状态更新函数
- Update()
- 协程
- LateUpdate()
- 渲染阶段
- 五、结束阶段
- 总结
前言
总所周知,Unity在执行脚本时会执行各种事件函数,诸如Start、Update这样的常见的事件函数。
本章将针对Unity事件函数进行一个时间序列上和作用的剖析。
本章对应的Unity官方文档链接(Unity版本21.3)
总览↓
一、Initializtion阶段
场景开始时将调用以下函数:
Awake()
Awake:始终在任何 Start 函数之前并在实例化预制件之后调用此函数。(如果游戏对象在启动期间处于非活动状态,则在激活之后才会调用 Awake。)
OnEnable()
OnEnable():(仅在对象处于激活状态时调用)在启用对象后立即调用此函数。在创建 MonoBehaviour 实例时(例如加载关卡或实例化具有脚本组件的游戏对象时)会执行此调用。
对于添加到场景中的对象,在为任何对象调用 Start 和 Update 等函数之前,会为所有脚本调用 Awake 和 OnEnable 函数。当然,在游戏运行过程中实例化对象时,不能强制执行此调用。
二、Editor阶段
Reset()
Reset:调用 Reset 可以在脚本首次附加到对象时以及使用 Reset 命令时初始化脚本的属性。
当使用脚本为对象附加脚本时,可以使用此方法进行初始化。
OnValidate()
OnValidate:每当设置脚本的属性时都会调用 OnValidate,包括反序列化对象时,这可能发生在不同的时间,例如在编辑器中打开场景时和域重新加载后。
三、第一次更新帧时
Start()
Start:仅当启用脚本实例后,才会在第一次帧更新之前调用 Start。
对于场景资源中的对象,在为任何脚本调用 Update 等函数之前,将在所有脚本上调用 Start 函数。当然,在游戏运行过程中实例化对象时,不能强制执行此调用。
四、帧循环阶段
在游戏运行期间,会重复执行帧循环阶段的事件函数。
首先从FixedUpdate()开始循环
FixedUpdate()
FixedUpdate()可以用一种固定的时钟速率执行某些操作,其特点是可以不受渲染帧数率的影响。
Tip:FixedUpdate()和Update()不只是在执行时序上有先后区别下文将详细讨论。
随后会进行动画状态的更新
动画状态更新函数
- OnStateMachineEnter:在状态机更新 (State Machine Update) 步骤中,当控制器的状态机进行流经
Entry 状态的转换时,将在第一个更新帧上调用此回调。在转换到 StateMachine 子状态时不会调用此回调。
仅当动画图中存在控制器组件(例如,AnimatorController、AnimatorOverrideController 或
AnimatorControllerPlayable)时才会发生此回调。
注意:将此回调添加到 StateMachineBehaviour 组件会禁用多线程的状态机评估。 - OnStateMachineExit:在状态机更新 (State Machine Update) 步骤中,当控制器的状态机进行流经
Exit 状态的转换时,将在最后一个更新帧上调用此回调。在转换到 StateMachine 子状态时不会调用此回调。
仅当动画图中存在控制器组件(例如,AnimatorController、AnimatorOverrideController 或
AnimatorControllerPlayable)时才会发生此回调。
注意:将此回调添加到 StateMachineBehaviour 组件会禁用多线程的状态机评估。
触发动画事件 (Fire Animation Events):调用在上次更新时间和当前更新时间之间采样的所有剪辑中的所有动画事件。 - StateMachineBehaviour
(OnStateEnter/OnStateUpdate/OnStateExit):一个层最多可以有 3
个活动状态:当前状态、中断状态和下一个状态。使用一个定义 OnStateEnter、OnStateUpdate 或
OnStateExit 回调的 StateMachineBehaviour 组件为每个活动状态调用此函数。
依次针对当前状态、中断状态和下一个状态调用此函数。
仅当动画图中存在控制器组件(例如,AnimatorController、AnimatorOverrideController 或
AnimatorControllerPlayable)时才会执行此步骤。 - OnAnimatorMove:在每个更新帧中为每个 Animator 组件调用一次此函数来修改根运动 (Root Motion)。
StateMachineBehaviour(OnStateMove):使用定义此回调的 StateMachineBehaviour
在每个活动状态中调用此函数。 - OnAnimatorIK:设置动画 IK。为每个启用 IK pass 的 Animator Controller 层进行一次此调用。
仅当使用人形骨架时才会执行此事件。 - StateMachineBehaviour(OnStateIK):使用在启用 IK pass 的层上定义此回调的
- StateMachineBehaviour 组件在每个活动状态中调用此函数。
- WriteProperties:从主线程将所有其他动画属性写入场景。
执行完FixedUpdate操作之后,能够接收输入事件,诸如鼠标点击、KeyPress的事件。
随后会进入Update()事件函数。
Update()
进入之后首先会执行协程创建操作(若有协程)。
协程
Update 函数返回后将运行正常协程更新。协程是一个可暂停执行 (yield) 直到给定的 YieldInstruction 达到完成状态的函数。 协程的不同用法:
函数 | 执行时刻 |
yield | 在下一帧上调用所有 Update 函数后,协程将继续 |
yield WaitForSeconds | 在为帧调用所有 Update 函数后,在指定的时间延迟后继续 |
yield WaitForFixedUpdate | 在所有脚本上调用所有 FixedUpdate 后继续 |
yield WWW | 在 WWW 下载完成后继续 |
yield StartCoroutine | 将协程链接起来,并会等待 MyFunc 协程先完成 |
随后会进行动画状态更新(同上),然后进入LateUpdate()
LateUpdate()
LateUpdate:每帧调用一次 LateUpdate(在 Update完成后)。LateUpdate 开始时,在 Update 中执行的所有计算便已完成。
*三种Update的不同:
FixedUpdate:独立于帧率,以一个固定的程序内部时钟执行(默认为0.02s)。
Update:每帧调用一次 Update。这是用于帧更新的主要函数。
LateUpdate:只在Update执行之后执行,LateUpdate 开始时,在 Update 中执行的所有计算便已完成。
函数 | 执行时刻 |
FixedUpdate | Time.fixedDeltaTime(默认每秒50次) |
Update | 每帧执行一次 |
LateUpdate | Update执行完成之后 |
执行Update之后,将会进入画面渲染阶段。
渲染阶段
函数 | 执行时刻 |
OnPreCull | 在摄像机剔除场景之前调用。剔除操作将确定摄像机可以看到哪些对象。正好在进行剔除之前调用 OnPreCull |
OnBecameVisible/OnBecameInvisible | 对象变为对任何摄像机可见/不可见时调用 |
OnWillRenderObject | 如果对象可见,则为每个摄像机调用一次 |
OnPreRender | 在摄像机开始渲染场景之前调用 |
OnRenderObject | 所有常规场景渲染完成之后调用。此时,可以使用 GL 类或 Graphics.DrawMeshNow 来绘制自定义几何形状 |
OnPostRender | 在摄像机完成场景渲染后调用 |
OnRenderImage | 在场景渲染完成后调用以允许对图像进行后处理 |
OnGUI | 每帧调用多次以响应 GUI 事件。首先处理布局和重新绘制事件,然后为每个输入事件处理布局和键盘/鼠标事件 |
OnDrawGizmos | 用于在场景视图中绘制辅助图标以实现可视化 |
至此,帧循环阶段结束。
五、结束阶段
在此阶段,程序将进行收尾的操作。
函数 | 执行时刻 |
OnApplicationQuit | 在退出应用程序之前在所有游戏对象上调用此函数。在编辑器中,用户停止播放模式时,调用函数 |
OnDisable | 行为被禁用或处于非活动状态时,调用此函数 |
OnDestroy | 对象存在的最后一帧完成所有帧更新之后,调用此函数 |
总结
总的来说分为三个阶段,初始化、帧循环、结束阶段。
初始化阶段的函数只能执行一次,对脚本、游戏对象的初始化放在这一块的事件函数中。
帧循环阶段会有协程的操作、输入设备的监听、动画的更新、以及最重要的帧的更新操作。
结束阶段用于执行用户退出程序后的操作、游戏对象被销毁时的操作。
各个阶段各司其职,可以最大程度上节约资源消耗,并且让程序充满条理。