Unity MonoBehaviour 生命周期速查

这篇只查生命周期怎么放代码:哪里缓存组件、哪里订阅事件、哪里做物理、哪里清理。


快速索引

我想做什么 生命周期
缓存自己身上的组件 Awake
检查 Inspector 引用 Awake
订阅事件 OnEnable
取消事件 OnDisable
读取其它对象初始化后的数据 Start
普通每帧逻辑 Update
输入检测 Update
Rigidbody 移动 FixedUpdate
摄像机跟随 LateUpdate
停止协程 / Tween OnDisable
释放非 Unity 资源 OnDestroy
对象池每次取出初始化 自定义 OnSpawn / Init
对象池每次回收清理 自定义 OnDespawn / ResetState

0. 执行顺序速记

1
2
3
4
5
6
7
8
Awake                       # 对象创建后,本对象初始化
OnEnable # 对象启用,可能执行很多次
Start # 第一次 Update 前执行一次
FixedUpdate # 固定物理步长
Update # 每帧一次
LateUpdate # 每帧 Update 后
OnDisable # 禁用或销毁前,可能执行很多次
OnDestroy # 对象销毁时

1. Awake:缓存组件和本对象初始化

1
2
3
4
5
6
7
8
9
10
11
12
private Rigidbody rb;

private void Awake()
{
rb = GetComponent<Rigidbody>(); // 缓存本对象组件

if (rb == null)
{
Debug.LogError("缺少 Rigidbody", this); // 启动时尽早暴露问题
enabled = false;
}
}

适合:

1
2
3
4
GetComponent 缓存
字段初始化
Inspector 引用检查
创建本对象内部依赖

不适合:

1
2
3
读取其它对象 Start 才初始化的数据
启动复杂游戏流程
做耗时加载

2. OnEnable / OnDisable:事件成对处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void OnEnable()
{
GameEvents.PlayerDied += OnPlayerDied; // 启用时订阅
}

private void OnDisable()
{
GameEvents.PlayerDied -= OnPlayerDied; // 禁用时取消
}

private void OnPlayerDied()
{
Debug.Log("玩家死亡");
}

UI 事件:

1
2
3
4
5
6
7
8
9
private void OnEnable()
{
startButton.onClick.AddListener(OnStartClicked); // 打开界面绑定
}

private void OnDisable()
{
startButton.onClick.RemoveListener(OnStartClicked); // 关闭界面解绑
}

少用:

1
2
3
4
private void OnEnable()
{
startButton.onClick.AddListener(() => StartGame()); // 匿名回调不好移除
}

3. Start:跨对象初始化

1
2
3
4
5
6
7
8
9
10
private void Start()
{
player = FindObjectOfType<Player>(); // 低频查找可接受

if (player == null)
{
Debug.LogError("场景中缺少 Player", this);
enabled = false;
}
}

适合:

1
2
3
读取其它对象已经准备好的状态
场景初始对象查找
首帧前启动流程

仍然不建议依赖多个对象的 Start 顺序,严格顺序用 Bootstrap。

4. Update:每帧逻辑

1
2
3
4
5
6
7
8
9
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) // 输入检测放 Update
{
Jump();
}

cooldown -= Time.deltaTime; // 普通帧计时
}

Update 里慎用:

1
2
3
4
5
6
GameObject.Find                  # 每帧查找开销高
FindObjectOfType # 每帧查找开销高
GetComponent # 可提前缓存
new List / new Dictionary # 每帧分配 GC
LINQ # 容易产生 GC
Debug.Log # 刷屏卡顿

5. FixedUpdate:物理逻辑

1
2
3
4
5
private void FixedUpdate()
{
Vector3 velocity = moveInput * moveSpeed;
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime); // 物理移动
}

适合:

1
2
3
Rigidbody.MovePosition
Rigidbody.AddForce
物理检测

输入不要只放 FixedUpdate,可能漏按键;建议 Update 读输入,FixedUpdate 用输入结果。

6. LateUpdate:跟随和收尾

1
2
3
4
5
6
7
8
9
private void LateUpdate()
{
if (target == null)
{
return;
}

transform.position = target.position + offset; // 摄像机跟随
}

适合:

1
2
3
Camera 跟随
角色动画后修正
依赖其它 Update 结果的同步

7. OnDestroy:最终释放

1
2
3
4
5
private void OnDestroy()
{
saveHandle?.Dispose(); // 释放非 Unity 资源
saveHandle = null;
}

注意:

1
2
3
4
OnDestroy 时其它单例可能已经销毁
事件解绑优先放 OnDisable
协程停止优先放 OnDisable
不要在 OnDestroy 里启动新流程

8. 对象池生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
public void Init(int damage, Vector3 direction)
{
this.damage = damage; // 每次取出都重新赋值
this.direction = direction;
lifeTime = 3f;
}

public void ResetState()
{
damage = 0; // 回收前清空状态
direction = Vector3.zero;
lifeTime = 0f;
}

对象池别只靠:

1
2
Start                               # 只执行一次,第二次取出不会再跑
OnDestroy # 回收对象通常不会 Destroy

9. 生命周期模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public sealed class ExampleBehaviour : MonoBehaviour
{
private void Awake()
{
// 缓存组件、检查 Inspector 引用
}

private void OnEnable()
{
// 订阅事件、启动可重复启停的逻辑
}

private void Start()
{
// 跨对象初始化、首帧前准备
}

private void Update()
{
// 输入、普通每帧逻辑
}

private void FixedUpdate()
{
// Rigidbody 物理逻辑
}

private void LateUpdate()
{
// 摄像机、跟随、收尾
}

private void OnDisable()
{
// 取消事件、停止协程、停止 Tween
}

private void OnDestroy()
{
// 释放非 Unity 资源
}
}

10. 常见坑速查

1
2
3
4
5
6
Awake 读别人 Start 初始化的数据       # 顺序不稳定
OnEnable 订阅但 OnDisable 不取消 # 重复触发 / 旧对象响应
Update 每帧 FindObjectOfType # 性能差
FixedUpdate 里读 GetKeyDown # 可能漏输入
对象池只在 Start 初始化 # 第二次取出状态残留
OnDestroy 访问全局单例 # 单例可能已经销毁

系列导航