外观
Stage02|Engine Loop
Lesson019|schedule、Action、Tween、Animation 的时间由谁推进?
Tags: #Creator2.x #Stage02 #Scheduler #Action #Tween #AnimationDifficulty: ⭐⭐⭐⭐☆
一、本课目标
游戏中的“过一秒”“移动到某个位置”“播放一段动画”,本质上都需要时间推进。本课回答:
schedule、Action、Tween、Animation 看起来不同,为什么它们都必须依赖 Engine Loop?
完成本课后,你应该能够:
- 区分定时回调、属性插值、动作序列和动画采样。
- 理解这些系统都需要
dt或游戏时间。 - 判断暂停、节点禁用和时间缩放分别影响什么。
- 避免多个系统同时写同一 Transform 属性。
二、它们都是“时间驱动的状态变化”
text
当前状态
↓ dt 推进
计算新状态
↓
应用到 Node / Component
↓
下一帧继续例如移动动画:
text
起点 x = 0
目标 x = 100
持续时间 = 1 秒
本帧经过 dt
↓
计算当前进度
↓
得到新的 x区别只在于:谁保存状态、谁计算进度、谁应用结果。
三、schedule:到时间执行一次逻辑
ts
this.scheduleOnce(() => {
this.showResult();
}, 1);高层模型:
text
登记回调
↓
Scheduler 每帧推进时间
↓
达到 1 秒
↓
调用回调它适合:
- 延迟打开 UI。
- 周期性刷新。
- 低频轮询。
- 超时检查。
它不适合代替每帧连续运动或高频插值。
四、Action:描述可组合的动作
Action 更接近“对节点执行一段动作”:
ts
const move = cc.moveTo(1, 100, 0);
const wait = cc.delayTime(0.5);
const sequence = cc.sequence(move, wait);
this.node.runAction(sequence);内部需要保存:
text
动作当前进行到哪一步
已经经过多少时间
当前插值进度
下一个动作是什么每帧推进可以抽象成:
text
Action.update(dt)
↓
更新当前动作进度
↓
计算节点新状态
↓
节点 Transform 被修改Action 不是独立线程,它仍然依赖引擎主循环。
五、Tween:对属性进行插值
Tween 通常表达“让某个属性从 A 平滑到 B”:
ts
cc.tween(this.node)
.to(1, { scale: 2 })
.start();它需要处理:
text
开始值
目标值
持续时间
缓动函数
当前时间不同缓动函数只改变进度曲线:
text
linear:匀速
easeIn:开始慢
easeOut:结束慢
easeInOut:两端慢、中间快但无论曲线怎样,最终仍然要在每帧根据时间算出一个属性值。
六、Animation:按时间采样关键帧
Animation 更接近“在时间轴上采样关键帧”:
text
时间 0.0s:位置 A
时间 0.2s:位置 B
时间 0.5s:位置 C
时间 1.0s:位置 D每帧需要:
text
累计动画时间
↓
根据播放速度和循环规则取样
↓
插值关键帧
↓
写入绑定属性Animation 可能同时修改多个属性,因此更需要避免业务脚本在同一帧持续覆盖动画结果。
七、谁来推进这些系统
可以用这个简化模型理解:
text
Director.mainLoop(dt)
↓
时间调度器推进 schedule
↓
Action / Tween / Animation 根据 dt 更新
↓
ComponentScheduler 调用 update / lateUpdate
↓
Transform / Renderer 使用最终状态不同 Creator 2.x 小版本可能改变 Action、Animation、Scheduler 的具体调用位置。稳定结论是:
没有主循环推进,就没有正常的游戏时间,也就没有这些系统的持续变化。
八、游戏时间和真实时间
一个系统可能使用:
text
游戏时间:受 pause、timeScale、游戏规则影响
真实时间:来自平台时钟,不一定随游戏暂停例如:
- 战斗动画通常跟随游戏时间。
- 登录超时可能需要真实时间或网络时间。
- 后台恢复检测需要平台时间。
- UI 动画是否暂停取决于产品设计。
不要只看到 dt 就认为所有业务时间含义相同。
九、多个系统同时写同一个属性
错误组合:
text
Tween 写 node.x
Animation 写 node.x
Player.update 也写 node.x结果可能是:
- 位置抖动。
- 动画被覆盖。
- 结束回调时状态不符合预期。
- 顺序变化后出现不同结果。
更好的设计是指定单一写入者:
text
业务层决定目标状态
↓
动画层负责表现插值
↓
Renderer 只读取最终状态或者把业务位置和表现位置分开:
text
modelPosition:逻辑位置
visualPosition:动画表现位置十、暂停和停止的区别
text
pause:保留当前进度,之后继续
stop:结束当前动作,可能回到初始或结束状态
destroy:对象生命周期终止节点 active=false、组件 enabled=false、Action 暂停和 cc.game.pause() 可能作用于不同层级。
设计暂停功能时要列清楚:
text
角色输入是否暂停
动画是否暂停
倒计时是否暂停
网络心跳是否暂停
UI 提示是否继续不要用一个全局布尔值假设所有系统都应同样暂停。
十一、案例:倒计时结束后播放奖励动画
ts
startCountDown() {
this.scheduleOnce(() => {
this.showReward();
cc.tween(this.rewardNode)
.to(0.3, { scale: 1.2 })
.to(0.2, { scale: 1 })
.start();
}, 3);
}运行链路:
text
Scheduler 推进 3 秒倒计时
↓
回调触发
↓
showReward 修改 active / 数据
↓
Tween 被登记
↓
后续每帧推进 Tween
↓
修改 rewardNode.scale
↓
Renderer 显示动画结果如果界面在 3 秒内被关闭,应在 onDisable 中取消任务或检查对象状态,避免回调访问已经失效的节点。
十二、常见误区
误区一:Tween 自己有独立计时器
它通常依赖引擎帧推进,不是后台线程。
误区二:scheduleOnce 的 1 秒一定在精确 1.000 秒执行
它受帧回调、主线程阻塞和平台调度影响。
误区三:Animation 和业务脚本同时写同一属性没问题
最后结果取决于更新顺序和写入者,容易产生覆盖和抖动。
误区四:暂停游戏一定等于暂停所有网络和系统任务
全局暂停主要控制游戏主循环,平台回调和外部服务需要单独设计。
十三、练习与答案
练习
- schedule、Action、Tween、Animation 的共同点是什么?
- 为什么 Tween 仍然需要 mainLoop?
- 同一属性被 Animation 和业务 update 同时写会怎样?
- 倒计时 UI 关闭后,为什么要取消 schedule 或检查对象状态?
- 游戏暂停时,哪些时间任务可能应该继续?
参考答案
- 它们都需要时间输入,在每帧推进状态变化。
- Tween 要根据每帧 dt 计算插值结果。
- 结果可能互相覆盖或抖动,具体取决于更新顺序。
- 防止回调访问无效对象、重复刷新或产生泄漏。
- 例如网络保活、系统提示或后台恢复检测,但要按产品语义单独决定。
十四、Creator 2.4.x 可复现实验:四种时间驱动
在一个节点上同时创建 schedule、Action、Tween 和 Animation,给同一个 Label 或 Sprite 设置不同属性,分别记录开始、结束和取消时刻。示例:
ts
this.scheduleOnce(() => cc.log('[scheduleOnce]'), 1);
cc.tween(this.node).to(1, { opacity: 0 }).call(() => cc.log('[tween:done]')).start();
this.node.runAction(cc.sequence(cc.moveBy(1, 100, 0), cc.callFunc(() => cc.log('[action:done]'))));运行 2 秒后将节点设为 inactive,再重新启用。观察组件归属的 schedule、Action、Tween 和 Animation 是否暂停或继续,并检查重新启用后是否重复创建。不同系统对 active、pause 和 stop 的处理应以实验为准,不能仅凭“都是动画”推断。
故障诊断
| 现象 | 可能原因 |
|---|---|
| 动画结束回调执行两次 | start/runAction 被重复调用,或旧实例未 stop |
| 关闭页面后仍改 UI | 全局调度、Tween owner 未清理,或回调持有旧节点 |
| 同一属性抖动 | Animation、Tween、update 存在多个写入者 |
十五、版本边界
课程示例覆盖 Creator 2.4.x 常用 schedule、runAction、cc.tween 和 Animation 组件。Tween 的内部实现、Action 与 ComponentScheduler 的相对调用点以及暂停传播细节可能随版本变化;稳定工程原则是明确 owner、唯一写入者和对称取消。
十六、应用题与推导答案
题目: 一个倒计时在 timeScale=0 时应该停住,但网络超时仍要生效,怎样拆分?
答案: 倒计时显示使用游戏时间或组件 dt,受 timeScale 影响;网络超时使用真实时间源或平台计时器,由网络层独立管理。回到主线程后再检查页面 session,避免把两种时间语义混在同一个 schedule 中。
十七、本课总结
text
schedule 是时间到点执行回调。
Action 是可组合的动作过程。
Tween 是属性插值。
Animation 是时间轴采样。
它们都依赖 Engine Loop 推进。
同一属性应尽量只有一个明确的写入者。十八、下一课预告
text
Lesson020|dt、FPS、pause、resume 与 timeScale