Update & Lifecycle
在 Unity 主游戏循环中,Update 每帧运行,FixedUpdate 按固定 Physics 时间步运行,LateUpdate 则在所有 Update 之后执行。为角色移动、相机跟随与物理交互选对钩子,能避免画面抖动与逻辑错序;这是每个 MonoBehaviour 作者必须掌握的帧时序基础。
想象一下...
Update 是跟着 FPS 走的模拟时钟——适合眼睛看到的一切:移动、输入、AI。FixedUpdate 是节拍稳定(0.02s)的数字时钟——用于物理,避免运动抖动。LateUpdate 最后运行——Camera 等所有东西都移动完再跟随,避免一帧延迟。
概念详解
Update() 每帧运行——频率跟随 FPS
(30、60、120…)。移动/速度务必乘以 Time.deltaTime,才能在不同帧率下保持一致。
FixedUpdate() 按 Time.fixedDeltaTime
运行(默认 0.02s = 每秒 50 次)。与 FPS 无关。用于
Rigidbody.AddForce()、Rigidbody.velocity、Physics 查询。一帧渲染可能对应
0、1 或多次 FixedUpdate。
LateUpdate() 在该帧所有脚本的 Update() 之后运行。 适合 Camera 跟随(玩家已移动)、IK,以及任何必须“读取” Update 之后结果的逻辑。
OnGUI() 已过时。 Update 与 Coroutine:一次性或延迟逻辑 → 优先用 Coroutine,而不是在 Update 里每帧轮询。
示意图:一帧内的顺序
FixedUpdate() × N
N = 上一帧累积的 timestep — 通常 0 或 1,卡顿时可能更多
Update() — 所有脚本
Input、AI、游戏逻辑、动画触发
LateUpdate() — 所有脚本
Camera follow、IK、Update 后逻辑
动手步骤
在 Update 中始终乘以 Time.deltaTime
transform.position += speed * Time.deltaTime * direction —— 单位/秒,与 FPS 无关。
物理力 → FixedUpdate
把 AddForce() 和 velocity 放在 FixedUpdate。在 Update 读 Input,在 FixedUpdate 施加。
Camera 跟随 → LateUpdate
Camera 脚本用 LateUpdate,确保玩家已经移动——没有一帧延迟。
删除未使用的 Update
即使空的 Update 也有开销。不需要每帧时,优先用 Events 或 Coroutines。
交互模拟器
按 Run 逐步查看帧循环。可调整 FPS 与 Physics 频率。
FixedUpdate
0
calls
Update
0
calls
LateUpdate
0
calls
Render
0
frames
代码示例
基础把逻辑正确拆分到 Update、FixedUpdate 和 LateUpdate。
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float speed = 5f;
[SerializeField] private float jumpForce = 8f;
private Rigidbody _rb;
private Vector3 _moveDir;
private bool _jumpRequest;
void Awake() => _rb = GetComponent<Rigidbody>();
void Update()
{
// Input — every frame, store for FixedUpdate
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
_moveDir = new Vector3(h, 0, v).normalized;
if (Input.GetButtonDown("Jump")) _jumpRequest = true;
}
void FixedUpdate()
{
// Apply physics — do not use Time.deltaTime here
_rb.velocity = new Vector3(_moveDir.x * speed, _rb.velocity.y, _moveDir.z * speed);
if (_jumpRequest)
{
_rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
_jumpRequest = false;
}
}
}
public class CameraFollow : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private Vector3 offset = new Vector3(0, 5, -8);
void LateUpdate()
{
// After target.Update() — target pose is final
transform.position = Vector3.Lerp(transform.position,
target.position + offset, Time.deltaTime * 5f);
}
}代码示例
进阶对不必每帧运行的逻辑,优先用 Coroutine 而非 Update。
public class EnemyAI : MonoBehaviour
{
private Transform _player;
private float _attackCooldown = 2f;
void Start()
{
_player = GameObject.FindWithTag("Player").transform;
// AI tick every 0.2s instead of every frame → saves CPU
StartCoroutine(AIUpdateLoop());
StartCoroutine(AttackLoop());
}
// Run AI logic 5×/sec instead of 60×/sec
private IEnumerator AIUpdateLoop()
{
var wait = new WaitForSeconds(0.2f);
while (true)
{
UpdatePathfinding();
yield return wait;
}
}
private IEnumerator AttackLoop()
{
while (true)
{
yield return new WaitForSeconds(_attackCooldown);
if (Vector3.Distance(transform.position, _player.position) < 3f)
Attack();
}
}
}📌 快速记忆
- ▸Update 中所有移动都要
* Time.deltaTime - ▸物理 force/velocity → FixedUpdate(通常不需要 deltaTime)
- ▸Camera 跟随 → LateUpdate(等玩家移动完)
- ▸稀疏逻辑 → Coroutine + WaitForSeconds
⚠️ 常见错误
❌ 在 FixedUpdate 里移动 transform 却不用 Time.fixedDeltaTime
速度依赖物理频率——不一致
✅ 乘以 Time.fixedDeltaTime,或使用 MovePosition()
❌ 在 FixedUpdate 里读 Input
帧快时可能漏掉 GetKeyDown
✅ 在 Update 读 Input,存标志,在 FixedUpdate 施加