下图为Unity脚本生命周期流程图,图源:Unity官方手册
由图可知,Unity生命周期包含几个主要阶段:初始化阶段(Initialization),物理与输入阶段(Physics+Input Events),游戏逻辑更新(Game Logic),渲染与界面(Scene Rendering+Gizmo Rendering+GUI Rendering),结束与清理(Decommissioning)。
初始化阶段
1.Awake()
触发时机:
当 GameObject 激活且脚本实例化时立即调用,无论脚本自身是否启用。Awake()在脚本生命周期中只会被调用一次。
用途:
初始化与脚本状态无关的变量或引用(如 GetComponent),但依赖 GameObject 的激活状态。
2.OnEnable()
触发时机:
当 GameObject 处于激活状态(activeInHierarchy == true)且脚本组件被启用(enabled == true)时触发,包括以下场景:
GameObject 从非激活变为激活状态(SetActive(true))。
脚本组件从禁用变为启用状态(enabled = true)。
每当条件满足时调用一次OnEnable(),即OnEnable可能被多次调用。
用途:
注册事件或回调(确保对象激活时能接收通知)。
启动或重启协程(需手动调用 StartCoroutine())。
重置临时状态或数据。
3.Start()
触发时机:
当 GameObject 激活且脚本启用时,在首次 Update() 前调用,仅执行一次。
所有 Awake() 方法(包括其他对象)已执行完毕,但不同脚本的 Start() 执行顺序未定义。
用途:
初始化依赖其他对象或组件的逻辑(需确保依赖对象的初始化在 Awake() 中完成)。
执行单次设置(如游戏初始状态配置)。
物理与输入阶段
1.FixedUpdate()
触发时机:
按固定时间间隔(默认 0.02 秒)触发,与帧率无关。该值可在 Unity 的 Project Settings → Time → Fixed Timestep 中调整。
可能在一帧内多次调用以同步物理时间(如帧率过低时)。
触发顺序通常早于 Update(),但具体取决于物理计算需求。
用途:
处理所有与物理引擎相关的操作(如 Rigidbody 控制、力的施加)。
执行需要严格按固定时间步长更新的逻辑(如回合制计时)。
注意事项:
避免耗时操作,确保物理模拟流畅性。
物理属性修改(如速度、力)应在此完成,而非 Update()。
输入检测需在 Update() 中处理,再传递至 FixedUpdate()。
2.物理事件(如OnCollisionEnter(), OnTriggerEnter())
触发时机:
在物理引擎完成每步计算后触发,紧随 FixedUpdate() 之后,可能在一帧内多次触发(若多次调用 FixedUpdate())。
具体顺序:FixedUpdate() → 物理引擎更新 → OnCollision/TriggerXXX() → Update()
用途:
OnCollisionEnter():响应物理碰撞(如伤害计算、音效播放),需双方 Collider 未标记为 Trigger。
OnTriggerEnter():检测物体进入触发区域(如收集品、陷阱),需至少一方 Collider 标记为 Trigger。
OnCollisionStay()/OnTriggerStay():持续碰撞/触发时每帧调用。
OnCollisionExit()/OnTriggerExit():碰撞/触发结束时调用。
注意事项:
确保至少一个物体带有 Rigidbody(触发器可接受运动学 Rigidbody)。
避免在物理事件中执行耗时操作,必要时使用缓存或协程。
通过碰撞矩阵优化检测,减少不必要的交互。
3.输入事件(如OnMouseXXX())
触发时机:
以OnMouseDown()为例,Unity 通过射线检测判断鼠标点击是否落在目标 Collider 上,若满足条件,OnMouseDown()在鼠标按下(Button Down)的同一帧触发。
用途:
点击选择物体(如拾取道具、选中角色)。
触发点击动画或音效(如按钮反馈)。
游戏逻辑更新
1.Update()
触发时机:
每帧调用一次:Update() 的调用频率与游戏帧率(FPS)直接相关。例如,若帧率为 60 FPS,则每秒调用 60 次。
帧率依赖性:由于调用间隔不固定(由 Time.deltaTime 表示),需通过时间增量来平滑逻辑(如移动速度),避免帧率波动导致行为不一致。
用途:
非物理游戏逻辑,例如:
输入检测:轮询键盘、鼠标或触摸输入(如 Input.GetKeyDown)。
动画控制:更新角色动画状态或 UI 动画。
状态管理:处理游戏逻辑的实时状态切换(如技能冷却、AI 决策)。
Transform 操作:直接修改物体的位置、旋转或缩放(如非物理驱动的移动)。
避免高开销操作:
减少在 Update() 中执行复杂计算(如物理射线检测、资源加载)。
使用标志位或缓存机制优化高频逻辑(如利用标志位设置每5帧执行一次)。
2.LateUpdate()
触发时机:
严格顺序:LateUpdate() 在 同一帧内所有 Update() 方法执行完毕后调用,包括其他脚本的 Update()。
帧率依赖:与 Update() 相同,调用频率取决于游戏帧率(FPS),每帧调用一次。
用途:
摄像机跟随:确保摄像机更新发生在玩家/目标物体的位置计算之后,避免画面抖动。
依赖其他对象最终状态的逻辑:
UI 元素对齐:在物体位置更新后调整 UI 的屏幕坐标(如血条跟随角色)。
物理模拟后的处理:如记录物体移动轨迹(需基于最终位置)。
渲染前的最后调整,材质或光照更新:根据场景状态动态修改渲染参数(如雾效强度)。
跨脚本顺序控制,确保数据同步:若 Script A 的 Update() 修改数据,Script B 的 LateUpdate() 可安全使用该数据。
渲染与界面
1.渲染事件(OnBecameVisible(),OnBecameInvisible(),OnRenderObject()等)
触发时机:
与摄像机渲染流程强相关:
所有事件均围绕摄像机的渲染管线阶段触发,例如:
渲染前:OnPreRender()、OnWillRenderObject()
渲染中:OnRenderObject()
渲染后:OnPostRender()、OnRenderImage()
可见性变化:OnBecameVisible()、OnBecameInvisible()
事件触发顺序严格遵循Unity的渲染生命周期逻辑。
依赖对象的渲染状态:
OnBecameVisible()/OnBecameInvisible() 依赖对象的Renderer组件激活性改变。
OnWillRenderObject()仅在对象被摄像机实际渲染前触发。
OnPreRender()、OnPostRender()、OnRenderImage()需绑定到摄像机对象。
帧率依赖性:
所有事件均在每帧的渲染阶段触发。
用途:
性能优化:
OnBecameVisible()/OnBecameInvisible():动态管理不可见对象的资源(如禁用粒子效果)。
OnPreRender()/OnPostRender():控制摄像机渲染目标,减少冗余计算。
自定义渲染逻辑:
OnRenderObject():手动绘制几何体(如调试线框)。
OnRenderImage():实现后处理效果(如模糊、调色)。
OnWillRenderObject():逐摄像机修改材质属性(如动态 LOD)。
资源管理:
按需加载/卸载资源(如OnBecameVisible()加载高清纹理)。
清理临时渲染资源(如OnPostRender()释放 RenderTexture)。
2.OnDrawGizmos()
OnDrawGizmos()是个特殊的生命周期函数,仅在编辑器模式下生效。
触发时机:
当场景视图发生以下操作时,OnDrawGizmos() 会被调用:
摄像机移动、旋转或缩放。
对象被选中或取消选中。
场景视图的任意更新(如脚本修改对象属性)。
在编辑器模式下,无论是否运行游戏,均会触发。
在发布后的游戏中,Gizmos 不会渲染。
用途:
调试与可视化,如绘制辅助线(标记路径、范围或方向)、显示碰撞体(可视化碰撞体形状,无需运行即可观察)、标记关键点(如生成点、触发器位置、AI 路径点等)。
开发工具扩展,自定义编辑器工具,为插件或工具绘制可视化界面(如地形编辑器的笔刷范围)
快速验证逻辑,在编辑模式下直接观察算法结果(如网格生成、寻路节点)。
注意事项:
避免在 OnDrawGizmos() 中执行复杂计算或高频绘制大量图形,否则会降低编辑器流畅度。
3.OnGUI()
触发时机:
OnGUI() 的触发与 Unity 的事件处理循环(Event Processing) 紧密相关,而非简单的“每帧多次”。具体规则如下:
每帧至少调用一次:用于处理基本的 GUI 渲染和事件响应。
事件触发额外调用:当有输入事件(如鼠标点击、键盘按下)发生时,Unity 会多次调用 OnGUI() 以处理事件传播。
用途:
调试工具:在开发过程中,OnGUI()可以快速创建按钮、标签等 UI 元素,方便调试和测试游戏逻辑,也可用于显示变量、状态等实时信息,帮助开发者监控游戏运行情况。
原型设计简单UI:在项目初期,OnGUI 可以快速搭建简单的 UI 原型。
编辑器工具:用于创建自定义编辑器窗口或 Inspector 扩展,方便资源管理和配置。
注意事项:
OnGUI 每帧调用,频繁使用可能影响性能,不适合复杂 UI。现在更推荐使用UGUI或UI Toolkit替代进行UI开发。
结束与清理
1.OnDisable()
触发时机:
gameobject被禁用,即调用gameObject.SetActive(false)时,该对象以及其子对象所有脚本的OnDisable()会被触发。
脚本被禁用时,该脚本的OnDisable()会被调用。
对象被销毁或场景卸载时未被标记为DontDestroyOnLoad,在OnDestroy()前触发OnDisable()
用途:
清理资源和终止逻辑,包括取消事件订阅、停止协程与定时器、释放临时资源,以及重置状态以确保下次启用时处于初始状态。
2.OnDestroy()
触发时机:
调用 Destroy(gameObject) 或 Destroy(this) 时,触发对应对象的 OnDestroy()。
当场景切换且对象未标记为 DontDestroyOnLoad 时,场景中的对象会被销毁。
应用退出时,所有活动对象的 OnDestroy() 会被触发(但非强制终止的情况,如崩溃时可能不会调用)。
通过 Destroy(component) 移除脚本组件时,该组件的 OnDestroy() 会被调用,但 GameObject 仍存在。
用途:
主要用于执行对象销毁前的清理操作,包括释放资源(内存释放等)、取消事件订阅、停止协程或Invoke定时任务,另外可用于保存数据,但应用非正常退出时可能无法触发。
3.OnApplicationQuit()
触发时机:
应用程序正常退出时调用,Unity编辑器模式下点击Play按钮中止播放时调用。注意在移动端切换到后台时不调用OnApplicationQuit(),而是调用OnApplicationPause(true)。当程序崩溃或强制中止(如在任务管理器关闭进程)不调用OnApplicationQuit()。
用途:
保存数据,终止协程、计时与线程,释放与清理资源,发送退出事件。