事件函数的执行顺序
Understand the execution order of Unity's built-in event functions so you can respond to events and update the state of your game in the right order.
阅读时间16 分钟最后更新于 1 个月前
事件函数是一组内置事件,MonoBehaviour 脚本可通过实施相应方法(通常称为回调)来订阅这些事件。回调对应于核心 Unity 子系统中的事件,例如物理、渲染和用户输入,或者对应于脚本自身生命周期的各个阶段,例如脚本的创建、激活、依赖于帧和独立于帧的更新和销毁。事件发生时,Unity 会在脚本上调用关联的回调,从而有机会实现逻辑以响应事件。
如果 Unity 引发这些事件并按预定顺序调用关联的 MonoBehaviour 回调,则该顺序会被记录在此处。了解执行顺序非常重要,这样您就不会尝试用一个回调来执行依赖于另一个尚未调用的回调的工作。但请注意,有些回调是针对事件,例如由用户输入触发的事件,这些事件可能在游戏运行时随时发生。应结合 MonoBehaviour 脚本参考(此处的事件回调列在 Messages 下)参阅此页面,以全面了解各个事件的含义和限制。
脚本生命周期概述
下图概括了 Unity 如何在脚本的生命周期内对事件函数进行排序以及重复执行这些事件函数。
有关各种事件函数的更多信息,请参阅以下部分:
流程图的范围
以下流程图的范围仅限于内置事件函数,您可以执行 MonoBehaviour 脚本参考中的 Messages 下记录的相应回调在任何 MonoBehaviour 脚本,从而订阅这些函数。此外,还显示了引发事件的子系统的一些其他内部方法,以供参考。
除了这些内置事件函数之外,还可以在脚本中订阅许多其他事件。Application、SceneManager 和 Camera 等几个主要类提供了可以注册自己的回调方法的委托。RuntimeInitializeOnLoadMethodAttribute 等方法属性也可用于在场景的特定阶段执行方法。请参阅您感兴趣的组件或子系统的脚本参考,了解您可以订阅的事件回调及其执行顺序的详细信息。
脚本生命周期流程图
注意:有些浏览器不支持 SVG 图像文件。如果以上图像未正确显示(例如,如果看不到任何文字),请尝试其他浏览器,例如 Google Chrome 或 Mozilla Firefox。
一般原则
通常,不应依赖于为不同游戏对象调用相同事件函数的顺序,除非该顺序有明确记录或可设置。如果需要更精细地控制播放器循环,可以使用 PlayerLoop API。
为同一 MonoBehaviour 子类的不同实例调用事件函数时,不能指定顺序。例如,一个 MonoBehaviour 的 函数可能会在另一个游戏对象(包括其父级或子级游戏对象)上的相同 MonoBehaviour 的 函数之前或之后调用。
UpdateUpdate可以指定一个 MonoBehaviour 子类的事件函数应在不同子类的事件函数之前调用(使用 Project Settings 窗口的 Script Execution Order 面板)。例如,如果有两个脚本,EngineBehaviour 和 SteeringBehaviour,可以设置 Script Execution Order(脚本执行顺序),这样 EngineBehaviours 始终在 SteeringBehaviours 之前更新。如果以累加方式加载多个场景,则配置好的脚本执行顺序将一次完整地应用于一个场景,而不是跨场景部分应用,因此 EngineBehaviour 和 SteeringBehaviour 都会在一个场景上更新,然后再在下一个场景上更新。
加载第一个场景
场景开始时将调用以下函数(为场景中的每个对象调用一次)。
- :当对象的新实例被创建时调用的第一个生命周期函数。始终在任何
Awake函数之前调用。如果游戏对象在启动期间处于非活动状态,则在激活之后才会调用Start。Awake - :在对象启用并处于活跃状态时调用,始终在
OnEnable之后(在同一对象上)和任何Awake之前调用。Start
对于属于场景资源一部分的对象,在为其中_任何_脚本调用 和后续函数之前,会为_所有_脚本调用 和 函数。但是,在实例化对象运行时无法强制执行此设置。
StartAwakeOnEnable仅在每个单独对象的作用域内,才能保证 在 之前被调用。多个对象之间的调用顺序不确定,不能指望在一个对象的 之前调用另一个对象的 。任何依赖于为场景中的所有对象调用 的工作都应在 中完成。
AwakeOnEnableOnEnableAwakeAwakeStart在加载和卸载场景之前
上图中未显示的 SceneManager.sceneLoaded 和 SceneManager.sceneUnloaded 事件,可以分别在加载和卸载场景时接收回调。请参阅相关脚本参考页面以了解详情和示例用法。对于场景中的所有对象,可以预期在 之后但在 之前收到 通知。请参阅禁用 Domain 和 Scene Reload 的详细信息以了解包含场景加载作为执行流程一部分的图表。
OnEnableStartsceneLoaded还可以使用 RuntimeInitializeOnLoadMethodAttribute 及其类型 BeforeSceneLoad 和 AfterSceneLoad,分别让使方法在场景加载之前或之后运行。请参阅 RuntimeInitializeOnLoadMethodAttribute 脚本参考主页,了解以这些类型标记的方法的执行顺序。
Editor
- : 在脚本首次附加到对象上时以及使用 Reset 命令时被调用,用于初始化脚本的属性。
Reset - : 每当设置脚本的属性时都会调用 OnValidate,包括反序列化对象时,这可能发生在不同的时间,例如在编辑器中打开场景时和域重新加载后。
OnValidate
在第一次帧更新之前
- : 仅当启用脚本实例后,才会在第一次帧更新之前调用 Start。
Start
对于场景资源中的对象,在为任何脚本调用 等函数之前,将在所有脚本上调用 函数。但是,在游戏运行过程中实例化对象时,不能强制执行此调用。例如,如果从一个对象的 函数实例化另一个对象,则在 首次在原始对象上运行之前,无法调用实例化对象的 。
UpdateStartUpdateUpdateStart帧之间
- :在检测到暂停的帧结束时调用,实际上是在正常帧更新之间调用。在调用
OnApplicationPause之后,将发出一个额外帧,从而允许游戏显示图形来指示暂停状态。OnApplicationPause
更新顺序
跟踪游戏逻辑和交互、动画、摄像机位置等的时候,可以使用一些不同事件。常见方案是在 函数中执行大多数任务,但是也可以使用其他函数。
Update-
以固定的游戏内时间间隔发生,并不是每帧发生。由于这些更新是固定的,并且帧率是可变的,因此当帧率很高时,一帧中可能没有固定更新,当帧率很低时,每帧可能有多个固定更新。所有物理计算和更新都在
FixedUpdate后立即进行,并且由于独立于帧率,因此在计算FixedUpdate中的移动时无需将值乘以 Time.deltaTime。固定更新发生的时间间隔由 Time.fixedDeltaTime 定义,可以直接在脚本中或通过 Editor 的 Time 设置中的 Fixed Timestep 属性进行设置。更多信息,包括用于确定是否执行FixedUpdate或Update的时间计算,请参阅 Time。FixedUpdate -
每帧调用一次。这是用于帧更新的主要函数。
Update -
每帧调用一次(在
LateUpdate完成后)。在Update开始时,在LateUpdate中执行的任何计算都会完成。Update的一种常见用途是用于后置的第三人称视角摄像机。如果在LateUpdate内让角色移动和转向,可以在Update中执行所有摄像机移动和旋转计算。这样可以确保角色在摄像机跟踪其位置之前已完全移动。LateUpdate
动画更新循环
在派生自 MonoBehaviour 的脚本上调用以上流程图中显示的以下动画循环回调:
在派生自 StateMachineBehaviour 的脚本上调用其他与动画相关的事件函数:
- StateMachineBehaviour.OnStateMachineEnter
- StateMachineBehaviour.OnStateMachineExit
- StateMachineBehaviour.OnStateEnter
- StateMachineBehaviour.OnStateUpdate
- StateMachineBehaviour.OnStateExit
- StateMachineBehaviour.OnStateMove
- StateMachineBehaviour.OnStateIK
有关这些回调的含义和限制,请参阅相关的脚本参考页面。
流程图中显示的其他动画函数是动画系统的内部函数,仅供参考。这些函数具有相关的 Profiler 标记,因此您可以使用 Profiler 查看 Unity 在帧中调用这些函数的时间。知道 Unity 调用这些函数的时间有助于准确了解所调用的事件函数的具体执行时间。有关动画函数和 Profiler 标记的完整执行顺序,请参阅 Profiler 标记。
渲染
此执行顺序仅适用于内置渲染管线。有关基于可编程渲染管线的渲染管线执行顺序的详细信息,请参阅通用渲染管线或高清渲染管线文档的相关部分。如果要在渲染之前立即执行操作,请参阅 Application.onBeforeRender。
- :在摄像机剔除场景之前调用。剔除确定哪些对象对摄像机可见。在剔除之前会调用
OnPreCull。OnPreCull - /
OnBecameVisible:当对象对任何摄像机可见/不可见时调用。上面的流程图中未显示OnBecameInvisible,因为对象可能随时变得不可见。OnBecameInvisible - :如果对象可见,则为每个摄像机调用一次。
OnWillRenderObject - :在摄像机开始渲染场景之前调用。
OnPreRender - :完成所有常规场景渲染之后调用。此时,可以使用 GL 类或 Graphics.DrawMeshNow 来绘制自定义几何形状。
OnRenderObject - :在摄像机完成场景渲染之后调用。
OnPostRender - :在场景渲染完成后调用以允许对图像进行后处理,请参阅后处理效果。
OnRenderImage - :每帧调用多次以响应 GUI 事件。首先处理布局和重新绘制事件,然后为每个输入事件处理布局和键盘/鼠标事件。
OnGUI - 用于在场景视图中绘制辅助图标以实现可视化。
OnDrawGizmos
注意:OnPreCull、OnPreRender、OnPostRender 和 OnRenderImage 是在 MonoBehaviour 脚本上调用的内置 Unity 事件函数,但仅当这些脚本附加到与已启用的摄像机组件相同的对象上时,才调用这些函数。如果要接收附加到不同对象的 MonoBehaviour 上的 、 和 的等效回调,必须使用等效委托(注意名称中的小写 )Camera.onPreCull、Camera.onPreRender 和 Camera.onPostRender,如脚本参考相关页面的代码示例所示。
OnPreCullOnPreRenderOnPostRenderon协程
常规的协程更新会在 函数返回之后运行。协程是一种函数,它能够暂停自身的执行 () 直到给定的 YieldInstruction 执行完毕。
Updateyield协程的不同用法:
- 在下一帧上调用所有
yield函数后,协程将继续。Update - 在为帧调用所有
yield WaitForSeconds函数后,在指定的时间延迟后继续协程。Update - 在所有脚本上调用所有
yield WaitForFixedUpdate后继续。如果协同程序在FixedUpdate之前生成,那么它会在当前帧的FixedUpdate之后继续运行。FixedUpdate - 在 WWW 下载完成后继续。
yield WWW - 链接协程,以便如果理论协程
yield StartCoroutine以coroutineA启动另一个yield StartCoroutine(coroutineB());,则coroutineB暂停,并等待coroutineA完成,然后再继续。有关示例,请参阅 MonoBehaviour.StartCoroutine。coroutineB
销毁对象时
- :此函数会在对象存在的最后一帧的所有帧更新完成后被调用(该对象可能应 Object.Destroy 要求或在场景关闭时被销毁)。
OnDestroy
退出时
在场景中的所有活动对象上调用以下函数:
- :在退出应用程序之前,在所有游戏对象上调用此函数。在编辑器中,用户停止播放模式时,调用函数。
OnApplicationQuit - :行为被禁用或处于非活动状态时,调用此函数。
OnDisable