Skip to content

Stage02|Engine Loop ​

Lesson017|update、lateUpdate 与 executionOrder ​

Tags: #Creator2.x #Stage02 #update #lateUpdate #executionOrderDifficulty: ⭐⭐⭐⭐☆


一、本课目标 ​

本课解决:

多个组件都在每帧工作时,谁先执行、谁后执行,业务应该依赖什么?

重点理解:

  • update 和 lateUpdate 的职责差异。
  • 为什么不能依赖节点树视觉顺序猜 update 顺序。
  • execution order 适合解决什么问题,不适合解决什么问题。
  • 如何减少组件之间的隐式时间依赖。

二、update 和 lateUpdate 的核心区别 ​

text
update:本帧主要状态计算
lateUpdate:读取其他对象结果后的后置处理

例如:

ts
// PlayerMove.ts
update(dt: number) {
    this.node.x += this.speed * dt;
}

// CameraFollow.ts
lateUpdate() {
    this.node.x = this.target.x;
}

这里的设计意图是:Player 先更新,Camera 再读取结果。

但 lateUpdate 不是替代所有 update 的“更晚 update”。如果逻辑不依赖其他组件的本帧结果,应该继续使用 update。


三、为什么节点树顺序不是可靠契约 ​

编辑器里看到:

text
Root
├── A
└── B

不代表 A 上脚本的 update 一定永远先于 B。实际顺序可能受:

  • 组件注册时机。
  • 组件执行顺序配置。
  • 调度器内部阶段。
  • 动态启用和禁用。
  • 场景加载顺序。

影响。

因此不要写出依赖“Hierarchy 上谁在上面”的业务逻辑。


四、executionOrder 适合解决什么问题 ​

当两个脚本确实存在稳定的阶段依赖,可以使用引擎提供的执行顺序配置或建立显式管理器:

text
输入采集系统
    ↓
角色状态系统
    ↓
相机跟随系统
    ↓
UI 表现系统

execution order 适合表达少量、明确、稳定的优先级关系。

它不适合:

  • 给几十个脚本排出复杂全序。
  • 代替模块之间的数据接口。
  • 掩盖循环依赖。
  • 让系统依赖一个难以维护的数字表。

如果必须配置大量数字才能让项目工作,通常说明系统边界需要重构。


五、一个典型的错误依赖 ​

ts
// A.ts
update() {
    this.value += 1;
}

// B.ts
update() {
    this.label.string = String(this.value);
}

如果 B 在 A 之前执行,UI 会慢一帧;如果顺序改变,结果又不同。这种隐式依赖很难排查。

更清晰的方式是由一个控制器统一组织:

ts
update(dt: number) {
    this.model.update(dt);
    this.view.refresh(this.model);
}

或者明确使用事件、数据快照和阶段接口。


六、lateUpdate 的常见应用 ​

1. 相机跟随 ​

ts
lateUpdate() {
    this.camera.position = this.target.position;
}

2. UI 同步 ​

ts
lateUpdate() {
    this.hpBar.progress = this.player.hp / this.player.maxHp;
}

3. 读取物理或动画结果 ​

ts
lateUpdate() {
    this.effect.position = this.actor.position;
}

这些案例共同点是:当前脚本需要读取本帧其他系统先产生的状态。


七、lateUpdate 也可能带来问题 ​

如果所有脚本都把逻辑放到 lateUpdate,会导致:

  • 逻辑阶段失去层次。
  • 不清楚谁先产生数据。
  • 仍然存在同阶段内的顺序依赖。
  • 本帧渲染前堆积大量工作。

正确原则不是“依赖别的组件就用 lateUpdate”,而是先明确数据生产和消费关系,再选择:

text
显式调用
事件
数据快照
阶段管理器
executionOrder
lateUpdate

lateUpdate 只是其中一种工具。


八、当前帧修改状态的可见时机 ​

text
update 修改 Player
    ↓
lateUpdate 读取 Player
    ↓
Transform / Render 读取结果

这是一种常见的教学模型。实际模块可能在自己的阶段更新,例如 Animation、Physics、Action 和 UI Layout 不一定都由同一个脚本阶段完成。

遇到“为什么读取到旧值”时,要问:

  1. 这个值由哪个系统产生?
  2. 产生系统在本帧什么时候执行?
  3. 当前脚本在哪个阶段读取?
  4. 读取的是本地值、世界值还是渲染缓存?

九、案例:相机跟随抖动 ​

错误结构:

text
Player.update 修改位置
Camera.update 读取位置
动画系统稍后又修改 Player

可能出现:

  • 相机跟随慢一帧。
  • 角色和相机不同步。
  • 动画和业务互相覆盖位置。

改进思路:

text
输入
→ 角色逻辑
→ 动画 / 物理结果
→ 相机 lateUpdate
→ 渲染

并且明确谁是 Player 位置的最终写入者,避免多个系统同时写同一属性。


十、常见误区 ​

误区一:lateUpdate 一定在所有 update 之后且所有组件都已完成 ​

它只能说明脚本生命周期阶段的相对意图,其他引擎模块仍可能有独立阶段。

误区二:executionOrder 数字越小越晚 ​

不要凭记忆猜具体数值规则,应以 Creator 2.x 当前版本文档或源码确认。

误区三:给所有脚本设置顺序就能解决架构问题 ​

复杂的全序依赖通常说明模块耦合过高。

误区四:同一 Node 上的组件一定按书写顺序更新 ​

组件顺序不能替代明确的调度契约。


十一、练习与答案 ​

练习 ​

  1. 什么时候适合使用 lateUpdate?
  2. 为什么不能依赖 Hierarchy 面板顺序?
  3. executionOrder 的正确使用边界是什么?
  4. 如何设计一个避免相机慢一帧的更新链?
  5. 如果多个系统同时写 Player.position,应该先解决什么问题?

参考答案 ​

  1. 当逻辑需要读取其他系统本帧产生的结果时。
  2. 实际调度还受注册、配置、动态状态和内部队列影响。
  3. 用于少量明确的阶段依赖,不用于掩盖复杂架构耦合。
  4. 明确输入、角色结果、动画/物理、相机和渲染的阶段顺序。
  5. 先确定唯一的状态生产者,其他系统通过数据读取或事件消费。

十二、Creator 2.4.x 可复现实验:验证更新阶段 ​

创建 Player、Camera、HUD 三个节点,分别挂载:

ts
// PlayerProbe
update() { this.node.x += 10; cc.log('[player:update]', this.node.x); }
// CameraProbe
lateUpdate() { cc.log('[camera:late]', this.node.x); }
// HudProbe
lateUpdate() { cc.log('[hud:late]', this.node.x); }

先不设置 executionOrder,记录多次运行结果;再只给 Player、Camera 设置明确的顺序,比较日志是否稳定。把 Camera 的读取从 update 改到 lateUpdate,观察相机跟随是否少一帧。实验重点不是背一个固定数字,而是找出“谁生产状态、谁消费状态、在哪个阶段消费”。

故障复现 ​

让 Player 和 Camera 都在 update 写同一个位置,故意制造抖动;然后改为 Player 唯一写入、Camera 只读并在 lateUpdate 同步。若仍抖动,检查 Animation、Tween、物理系统是否也是写入者。

十三、版本边界与诊断 ​

Creator 2.4.x 提供 executionOrder 作为组件级排序手段,但同一阶段内部的完整顺序、动态启用时的插入点和引擎系统相对位置不应靠猜测。精确判断时记录版本、平台、节点状态,并查看当前版本 ComponentScheduler 实现。Hierarchy 顺序不是稳定的业务契约。

十四、应用题与推导答案 ​

题目: 角色移动偶尔被 UI 脚本覆盖,应该先调大 UI 的 executionOrder 吗?

答案: 不应先改数字。先用日志记录所有写入 position 的调用点,确定唯一生产者;UI 应读取角色状态而不是写角色 Transform。只有在生产者和消费者边界明确后,才用 executionOrder 表达少量必要的阶段关系。否则排序只是把竞态隐藏到另一个数字里。

十五、本课总结 ​

text
update 负责本帧主要计算。
lateUpdate 适合读取其他系统结果并做后置同步。
Hierarchy 顺序不是完整的调度契约。
executionOrder 应少量、明确地使用。
复杂顺序依赖应通过数据和系统设计解决。

十六、下一课预告 ​

text
Lesson018|active、activeInHierarchy、enabled 在当前帧如何改变调度?