Unity生命周期函数笔记
生命周期函数是极其基础,极其重要,极其容易眼高手低的内容。
Awake()、OnEnable()、Start()
- 物体激活、脚本启用时,执行顺序是Awake、OnEnable、Start;
- 进入场景时失活的物体会执行Awake,不执行后两个;
- 进入场景时未启用脚本会执行Awake,不执行后两个;
- 当物体激活且脚本启用时执行OnEnable;
- Start可以理解为第一次OnEnable后执行;
deepseek回答:
在Unity中,未激活的物体不会执行Start方法24。具体执行规则如下:
-
初始未激活的物体:
- 若游戏物体初始状态为
active=false,其所有脚本的Awake和Start都不会执行6 - 只有当通过
SetActive(true)激活物体时,才会首次触发Awake和Start46
- 若游戏物体初始状态为
-
脚本启用状态的影响:
- Awake会在脚本实例加载时立即执行,即使脚本的
enabled=false2 - Start必须同时满足两个条件才会执行:
- 游戏物体处于激活状态(
activeInHierarchy=true) - 脚本组件启用(
enabled=true)26
- 游戏物体处于激活状态(
- Awake会在脚本实例加载时立即执行,即使脚本的
-
生命周期特性:
- Start在整个物体生命周期中只会执行一次56
- 若物体被反复激活/禁用,只有OnEnable/OnDisable会重复触发56
执行顺序总结:
- 首次激活物体时:Awake → OnEnable → Start35
- 后续禁用再激活时:仅触发OnEnable
多个脚本的Awake()、OnEnable()、Start()
按一个顺序,先一个一个脚本执行Awake、OnEnable,完毕后一个一个脚本执行Start。所以一个脚本的OnEnable可以在另一个Awake之前。假设A脚本执行顺序在B之前则:
A.Awake>A.OnEnable>B.Awake>B.OnEnable>A.Start>B.Start

一开始非激活的物体上面的脚本,开始运行时执行Awake吗?
不执行!激活时才执行。
a.Update()、a.LateUpdate()、b.Update()、b.LateUpdate()的执行顺序(假设a先执行)
a.Update>b.Update>a.LateUpdate>b.LateUpdate
这么记:如果a.LateUpdate紧贴a.Update执行,那么它完全可以放在Update里面,没有存在的必要。
一帧里Update、LateUpdate和协程的执行顺序
Unity一帧内Update与协程的执行顺序遵循以下规则:
-
基础执行顺序
- 每帧先执行所有脚本的Update()函数,再处理协程中
yield return之后的代码2 - 若协程使用
WaitForEndOfFrame,则会在所有LateUpdate()调用结束后执行1
- 每帧先执行所有脚本的Update()函数,再处理协程中
-
协程启动时机影响
- 在Awake/OnEnable中启动的协程会在首帧Update前被处理2
- 在Start中启动的协程需等到下一帧Update才会执行2
-
多协程优先级
-
后启动的协程会优先执行(LIFO原则)2
-
例如同时存在协程A和B,若先启动A后启动B,则当前帧会先执行B的后续代码2
-
-
特殊延迟类型
WaitForFixedUpdate:在FixedUpdate之后、Update之前执行14WaitForSeconds:受Time.timeScale影响,实际执行时机可能跨帧1
-
与LateUpdate的关系
- LateUpdate()始终在所有Update和常规协程执行完毕后调用34
- 但若协程包含
yield return null,其后续代码会在LateUpdate前执行1
- 物理帧同步
- FixedUpdate()独立于主线程帧率,每物理时间步长强制调用(默认0.02秒)1
- 当游戏卡顿时,Unity会通过补帧机制保证FixedUpdate按固定频率执行1
注:完整生命周期顺序为
Awake → OnEnable → Start → FixedUpdate → Update → 协程(非WaitForEndOfFrame) → LateUpdate→协程(WaitForEndOfFrame) → OnDisable → OnDestroy
且Update → 协程(非WaitForEndOfFrame) → LateUpdate→协程(WaitForEndOfFrame)是执行完所有脚本的上一个函数再执行所有脚本的下一个函数。
private void Update()
{
Debug.Log($"{name}更新{Time.frameCount}");
}
private void LateUpdate()
{
Debug.Log($"{name}晚更新{Time.frameCount}");
}

脚本执行顺序与OnDestroy


设置脚本执行顺序不能控制OnDestroy的执行顺序,据说销毁顺序主要受组件添加顺序影响。
是执行完所有脚本的Awake后执行OnEnable吗?
NO!不是所有Awake执行完才执行OnEnable
// 假设有 ScriptA 和 ScriptB 两个脚本
// 都在同一个GameObject上
public class ScriptA : MonoBehaviour
{
void Awake() => Debug.Log("A.Awake");
void OnEnable() => Debug.Log("A.OnEnable");
}
public class ScriptB : MonoBehaviour
{
void Awake() => Debug.Log("B.Awake");
void OnEnable() => Debug.Log("B.OnEnable");
}
A.Awake ← ScriptA的Awake
A.OnEnable ← ScriptA的OnEnable ❗注意这里!
B.Awake ← ScriptB的Awake
B.OnEnable ← ScriptB的OnEnable
1. 创建GameObject
↓
2. 添加第一个组件(如ScriptA)
↓
3. 调用 ScriptA.Awake()
↓
4. 调用 ScriptA.OnEnable() ← 立即调用!
↓
5. 添加第二个组件(如ScriptB)
↓
6. 调用 ScriptB.Awake()
↓
7. 调用 ScriptB.OnEnable()
对于不同GameObject:
场景加载
↓
GameObject1创建
↓
组件A.Awake() → 组件A.OnEnable() ← 成对执行!
↓
组件B.Awake() → 组件B.OnEnable() ← 成对执行!
↓
GameObject2创建
↓
组件C.Awake() → 组件C.OnEnable() ← 成对执行!
多脚本的OnDisable、OnDestroy顺序
先把一个脚本的OnDisable、OnDestroy执行完,再执行下一个脚本。
private void OnDisable()
{
Debug.Log($"{name}失活!");
}
private void OnDestroy()
{
Debug.Log($"{name}销毁!");
}

Update、OnAnimatorMove、OnAnimatorIK的执行顺序
其中动画的两个函数需要填入动画状态机,OnAnimatorIK需要填入Avatar。
public class 生命周期函数执行顺序:MonoBehaviour{
bool updated;
bool animatorMoved;
bool animatorIKed;
void Start(){
}
void Update(){
if(!updated){
Debug.Log("Update执行");
updated=true;
}
}
void OnAnimatorMove(){
if(!animatorMoved){
Debug.Log("OnAnimatorMove执行");
animatorMoved=true;
}
}
void OnAnimatorIK(int layerIndex){
if(!animatorIKed){
Debug.Log("OnAnimatorIK执行");
animatorIKed=true;
}
}
}

和事件监听的矛盾
有了上面的知识,我们知道一个脚本A要么比B先初始化,先销毁,要么比B后初始化,后销毁,而事件监听要求分发者先初始化,后销毁。

Instantiate、Awake、OnEnable、Start
Instantiate后立即Awake、OnEnable,下一帧Update前执行Start。

AddComponent、Awake
AddComponent后立即Awake,然后执行AddComponent后面的。
SceneManager.LoadSceneAsync、AsyncOperation.isDone、Awake、OnEnable、Start
如下的异步加载,发现preset在场景固有脚本的Start后执行。
IEnumerator MyLoadSceneAsync(string sceneName, UnityAction preset)
{
asyncOperation = SceneManager.LoadSceneAsync(sceneName);
yield return null;//保证加载面板的初始化函数执行完毕
while (!asyncOperation.isDone)
{
MyUIManager.Instance.GetPanel<PanelLoading>()
.progressBar.value = asyncOperation.progress;
yield return null;
}
//不用写销毁加载页面,切场景时会自动销毁
preset?.Invoke();
}
SceneManager运行时加载场景,Start里设置位置没有效果?
好像加载场景时有一个把所有对象的属性根据场景文件写入的过程。如果是在本场景直接运行,这个写入在打开场景时已经执行过,所以Start里赋值有效,而运行时加载场景,根据场景文件写入是在Start后面,覆盖了Start的效果。
对于自定义属性比如血量,没有这个问题。
然后把设置人物位置在协程里,延迟一段时间执行,Start里开启协程。也无效。
然后把人物CharacterController关闭,设置位置,再打开,设置有效。所以应该是CharacterController有某种记忆之前位置的机制,
public void SetCharacterPos(CharacterBase character,Vector3 pos)
{
character.characterController.enabled = false;
character.transform.position = pos;
character.characterController.enabled=true;
}
异步加载场景asyncOperation.isDone后立即执行和目标场景脚本Awake的顺序

先执行目标场景脚本Awake,后执行回调。
关于生命周期函数容易忽略的知识点
- 多个脚本的生命周期函数的顺序;
- 生命周期函数和Instantiate、AddComponent,乃至LoadScene、LoadSceneAsync的关系;
- 非激活物体脚本Awake是否执行;
- 生命周期函数和协程的顺序;
自测题
- 有脚本a、b,假设a的执行优先级更高,写出a、b的Awake、OnEnable、Start、Update、LateUpdate、OnDisable、OnDestroy的执行顺序。(a.Awake>a.OnEnable>b.Awake>b.OnEnable>a.Start>b.Start>a.Update>b.Update>a.LateUpdate>b.LateUpdate>a.OnDisable>a.OnDestroy>b.OnDisable>b.OnDestroy)
- Instantiate一个对象,上面挂有一个脚本a,写出a.Awake、a.OnEnable、a.Start和Instantiate下一行代码的执行顺序。(Instantiate>Awake>OnEnable>Instantiate后面>Start)
- 一个物体的位置被animator的关键帧控制了,Update里修改位置有效吗?LateUpdate里修改位置有效吗?
更多推荐
所有评论(0)