关于 time-and-timers
Phaser 4 时间与计时器指南,面向需要安排游戏内事件和时间流程的开发者。涵盖场景时钟、定时器及可组合回调、补间和声音等动作的时间线。
主要功能
- 01创建延迟与重复计时器
- 02暂停时钟或单个计时器
- 03调整时钟及事件时间倍率
- 04按绝对或相对时间编排时间线
- 05在时间线中触发回调、补间和声音
适用场景
- 01延迟触发场景切换
- 02按固定间隔生成敌人或发射子弹
- 03编排包含补间和声音的过场动画
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill time-and-timers安装后在 Claude Code 中输入/time-and-timers确认已加载。
详细介绍
时间与计时器
Phaser 4 中的 Clock 插件、TimerEvent、延迟、循环、Timeline 事件排序、暂停时间、时间缩放和增量时间。
关键源码路径: src/time/Clock.js、src/time/TimerEvent.js、src/time/Timeline.js、src/time/typedefs/、src/time/events/
相关技能: ../scenes/SKILL.md、../tweens/SKILL.md
快速开始
// In a Scene's create() method:
// One-shot delayed call (fires once after 1 second)
this.time.delayedCall(1000, () => {
console.log('One second later');
});
// Repeating timer (fires 5 times, once every 500ms)
this.time.addEvent({
delay: 500,
callback: () => { console.log('tick'); },
repeat: 4 // 4 repeats = 5 total fires
});
// Infinite loop timer
this.time.addEvent({
delay: 1000,
callback: this.spawnEnemy,
callbackScope: this,
loop: true
});
this.time 是场景的 Clock 实例(注册为键为 time 的 'Clock' 插件)。它创建并管理 TimerEvent 对象,这些对象根据游戏时间触发回调。
核心概念
Clock(this.time)
Clock 是一个场景级插件,用于跟踪游戏时间,并在每一帧更新其所有 TimerEvent。关键属性:
now—— 当前时间,单位为毫秒(等同于传递给场景update方法的time)。startTime—— 场景启动时的时间戳。timeScale—— 应用于增量时间的乘数。默认值为1。大于 1 的值会加快所有计时器;小于 1 的值会减慢它们;0会冻结时间。paused—— 当为true时,不会更新任何 TimerEvent。
Clock 监听 PRE_UPDATE(以刷新待处理的添加/移除)和 UPDATE(以推进活动事件)。它会随场景自动关闭并销毁。
TimerEvent
TimerEvent 会在每一帧累积经过的时间:elapsed += delta * clock.timeScale * event.timeScale。当 elapsed >= delay 时,回调会触发。在所有重复次数用尽后,该事件会在下一帧从 Clock 中移除。
通过配置设置的关键属性:delay、repeat、loop、callback、callbackScope、args、timeScale、startAt、paused。
Timeline(this.add.timeline)
Timeline 是一个用于在特定时间点调度动作的序列器。与 Clock(管理独立计时器)不同,Timeline 运行由绝对或相对时间戳键控的线性事件序列。
const timeline = this.add.timeline([
{ at: 0, run: () => { /* immediate */ } },
{ at: 1000, run: () => { /* at 1s */ } },
{ at: 2500, tween: { targets: sprite, alpha: 0, duration: 500 } }
]);
timeline.play();
Timeline 始终以 暂停 状态启动。你必须调用 play() 才能启动它们。它们通过 GameObjectFactory 创建,并在场景关闭时自动销毁。
常见模式
延迟调用
// Shorthand -- fires once, no repeat
this.time.delayedCall(2000, () => {
this.scene.start('GameOver');
});
有限次数的重复计时器
// repeat: 9 means 10 total fires (1 initial + 9 repeats)
const timer = this.time.addEvent({
delay: 200,
callback: this.fireBullet,
callbackScope: this,
repeat: 9
});
// Check progress
timer.getRepeatCount(); // repeats remaining
timer.getOverallProgress(); // 0..1 across all repeats
无限循环
const spawner = this.time.addEvent({
delay: 3000,
callback: this.spawnWave,
callbackScope: this,
loop: true
});
// Stop it later
spawner.remove(); // or spawner.paused = true to pause
设置 repeat: -1 等同于 loop: true。
使用 startAt 的首次触发快捷方式
// First fire happens quickly (after 100ms), then every 2s
this.time.addEvent({
delay: 2000,
callback: this.heartbeat,
callbackScope: this,
loop: true,
startAt: 1900 // pre-fill elapsed so first fire is at 100ms
});
停止并移除计时器
const timer = this.time.addEvent({ delay: 1000, loop: true, callback: fn });
// Option 1: Remove from clock (schedules removal next frame)
timer.remove(); // silently expires
timer.remove(true); // fires callback one last time, then expires
// Option 2: Remove via Clock
this.time.removeEvent(timer);
// Option 3: Remove all timers
this.time.removeAllEvents();
暂停与恢复
// Pause the entire Clock (all timers freeze)
this.time.paused = true;
this.time.paused = false;
// Pause a single timer
timer.paused = true;
timer.paused = false;
时间缩放(慢动作 / 快进)
// Slow all timers in this scene to half speed
this.time.timeScale = 0.5;
// Speed up a single timer to 2x
timer.timeScale = 2;
// Combined: effective scale = clock.timeScale * event.timeScale
// So 0.5 * 2 = 1x for that specific timer
读取计时器状态
timer.getProgress(); // 0..1 for current iteration
timer.getOverallProgress(); // 0..1 across all repeats
timer.getElapsed(); // ms elapsed this iteration
timer.getElapsedSeconds(); // seconds elapsed this iteration
timer.getRemaining(); // ms until next fire
timer.getRemainingSeconds(); // seconds until next fire
timer.getOverallRemaining(); // ms until final fire
timer.getOverallRemainingSeconds(); // seconds until final fire
timer.getRepeatCount(); // repeats left
Timeline:事件排序
const timeline = this.add.timeline([
{
at: 0,
run: () => { this.title.setAlpha(1); },
sound: 'intro'
},
{
at: 2000,
tween: { targets: this.title, y: 100, duration: 1000 },
sound: { key: 'whoosh', config: { volume: 0.5 } }
},
{
at: 4000,
set: { alpha: 0 },
target: this.title,
event: 'INTRO_DONE'
}
]);
timeline.on('INTRO_DONE', (target) => { /* custom event */ });
timeline.on('complete', (tl) => { /* all events ran */ });
timeline.play();
Timeline:使用 from 和 in 的相对计时
const timeline = this.add.timeline([
{ at: 1000, run: stepOne }, // absolute: 1s from start
{ from: 500, run: stepTwo }, // relative: 500ms after previous (1.5s)
{ from: 1000, run: stepThree } // relative: 1000ms after previous (2.5s)
]);
at—— 从 Timeline 开始的绝对毫秒数(默认 0)。in—— 从当前elapsed时间开始的偏移量(在向正在运行的 Timeline 添加事件时很有用)。from—— 相对于上一个事件开始时间的偏移量。
优先级:from 覆盖 in,in 覆盖 at。
Timeline:条件事件
const timeline = this.add.timeline([
{
at: 5000,
if: () => this.player.health > 0,
run: () => { this.showBonusRound(); }
}
]);
如果 if 回调返回 false,则跳过该事件(标记为已完成,但不执行动作)。
Timeline:循环
const timeline = this.add.timeline([
{ at: 0, run: () => console.log('start') },
{ at: 1000, run: () => console.log('end') }
]);
timeline.repeat().play(); // infinite loop
timeline.repeat(3).play(); // loop 3 additional times (4 total runs)
timeline.repeat(false).play(); // no looping
TimelineEventConfig 上的 loop 回调会在重复迭代时触发(首次运行时不触发)。
Timeline:暂停、恢复、停止、重置
timeline.pause(); // freezes elapsed counter and pauses spawned tweens
timeline.resume(); // resumes elapsed counter and unpauses spawned tweens
timeline.stop(); // sets paused=true, complete=true
timeline.reset(); // elapsed=0, all events marked incomplete, starts playing
timeline.isPlaying(); // true if not paused and not complete
timeline.getProgress(); // 0..1 based on events completed / total events
Timeline:时间缩放
timeline.timeScale = 2; // double speed
timeline.timeScale = 0.5; // half speed
注意:Timeline 的 timeScale 不会影响由该 Timeline 创建的补间动画。请单独设置补间动画的 timeScale。
Timeline:完整过场动画示例
Timeline 非常擅长编排包含混合动作(回调、补间、声音、属性设置和自定义事件)的过场动画:
class CutsceneScene extends Phaser.Scene {
create() {
const timeline = this.add.timeline([
{
at: 0,
run: () => { console.log('Start!'); }
},
{
at: 1000,
tween: {
targets: this.player,
x: 400,
duration: 500,
ease: 'Power2'
}
},
{
at: 1500,
sound: 'doorOpen'
},
{
at: 2000,
set: { visible: true },
target: this.door
},
{
from: 500,
run: () => { console.log('Relative timing'); }
},
{
at: 5000,
event: 'cutsceneDone',
stop: true
}
]);
timeline.on('cutsceneDone', () => {
this.scene.start('GameScene');
});
timeline.play();
}
}
计时器重置与复用
已完成的 TimerEvent 可以使用新配置重置,并重新添加到 Clock:
const timer = this.time.addEvent({
delay: 1000,
callback: this.phase1,
callbackScope: this,
repeat: 2
});
// Later, after it completes, reconfigure and re-add:
timer.reset({
delay: 500,
callback: this.phase2,
callbackScope: this,
repeat: 4
});
this.time.addEvent(timer); // must re-add; reset() alone does not schedule it
配置参考
TimerEventConfig
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
delay | number | 0 | 回调触发前的延迟毫秒数 |
repeat | number | 0 | 首次触发后重复的次数。使用 -1 表示无限 |
loop | boolean | false | 如果为 true,则无限重复(等同于 repeat: -1) |
callback | function | -- | 计时器触发时调用的函数 |
callbackScope | any | TimerEvent | 回调的 this 上下文 |
args | Array | [] | 传递给回调的额外参数 |
timeScale | number | 1 | 每个事件的时间乘数 |
startAt | number | 0 | 预填充已流逝的毫秒数(使首次触发更早发生) |
paused | boolean | false | 以暂停状态启动计时器 |
TimelineEventConfig
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
at | number | 0 | 从时间线开始的绝对毫秒时间 |
in | number | -- | 相对于当前已流逝时间的偏移(覆盖 at) |
from | number | -- | 相对于上一事件时间的偏移(覆盖 at 和 in) |
run | function | -- | 事件触发时调用的回调 |
loop | function | -- | 重复迭代时调用的回调(首次运行时不调用) |
if | function | -- | 守卫函数;返回 false 则跳过该事件 |
event | string | -- | 在 Timeline 实例上发出的事件名称 |
target | any | -- | run/loop/if 的作用域,以及 set 的目标 |
set | object | -- | 事件触发时应用于 target 的键值对 |
tween | TweenConfig | -- | 事件触发时创建的补间配置或实例 |
sound | string/object | -- | 要播放的音效键或 { key, config } |
once | boolean | false | 事件触发后移除此事件 |
stop | boolean | false | 此事件触发时停止整个时间线 |
TimelineEvent(内部)
存储在 timeline.events[] 中经过处理的事件对象。在配置基础上扩展了:
| 属性 | 类型 | 描述 |
|---|---|---|
complete | boolean | 此事件是否已触发 |
time | number | 解析后的绝对毫秒时间 |
repeat | number | 此事件已重复的次数 |
tweenInstance | Tween/TweenChain | 生成的补间引用(如果有) |
事件
时间线事件
| 事件 | 常量 | 监听器签名 | 触发时机 |
|---|---|---|---|
'complete' | Phaser.Time.Events.COMPLETE | (timeline) | 所有时间线事件均已运行完毕 |
通过 TimelineEventConfig 的 event 属性定义的自定义事件也会以 (target) 为签名在 Timeline 实例上发出。
TimerEvent / Clock
TimerEvent 和 Clock 不发出 EventEmitter 事件。计时器直接使用 callback 属性。Clock 由场景生命周期事件(PRE_UPDATE、UPDATE、SHUTDOWN、DESTROY)管理。
API 快速参考
Clock(this.time)
| 方法 | 签名 | 返回值 | 描述 |
|---|---|---|---|
addEvent | (config | TimerEvent) | TimerEvent | 创建并调度一个计时器事件 |
delayedCall | (delay, callback, args?, scope?) | TimerEvent | 一次性延迟调用的简写 |
removeEvent | (event | event[]) | this | 移除指定的计时器 |
removeAllEvents | () | this | 调度移除所有活动计时器 |
clearPendingEvents | () | this | 清除尚未加入活动列表的计时器 |
| 属性 | 类型 | 描述 |
|---|---|---|
now | number | 当前时钟毫秒时间 |
startTime | number | 场景启动的时间 |
timeScale | number | 所有计时器的增量乘数 |
paused | boolean | 冻结所有计时器 |
TimerEvent
| 方法 | 返回 | 描述 |
|---|---|---|
getProgress() | number | 当前迭代的 0..1 进度 |
getOverallProgress() | number | 所有重复次数的 0..1 总进度 |
getElapsed() | number | 本次迭代已流逝的毫秒数 |
getElapsedSeconds() | number | 本次迭代已流逝的秒数 |
getRemaining() | number | 距下次触发的毫秒数 |
getRemainingSeconds() | number | 距下次触发的秒数 |
getOverallRemaining() | number | 距最终触发的毫秒数 |
getOverallRemainingSeconds() | number | 距最终触发的秒数 |
getRepeatCount() | number | 剩余重复次数 |
remove(dispatchCallback?) | void | 使计时器过期(可选择触发回调) |
reset(config) | TimerEvent | 使用新配置重新初始化 |
destroy() | void | 清空回调引用 |
Timeline (this.add.timeline)
| 方法 | 签名 | 返回 | 描述 |
|---|---|---|---|
add | (config | config[]) | this | 向时间线追加事件 |
play | (fromStart?) | this | 开始播放(默认重置到开头) |
pause | () | this | 暂停时间线及其生成的补间动画 |
resume | () | this | 恢复时间线及其生成的补间动画 |
stop | () | this | 停止(设置暂停 + 完成) |
reset | (loop?) | this | 重置已流逝时间并将所有事件设为未完成 |
repeat | (amount?) | this | 设置循环次数(-1/true=无限,false=不循环) |
clear | () | this | 移除所有事件,重置已流逝时间,暂停 |
isPlaying | () | boolean | 若未暂停且未完成则为 true |
getProgress | () | number | 基于已完成事件数量的 0..1 进度 |
| 属性 | 类型 | 描述 |
|---|---|---|
elapsed | number | 当前已流逝时间,以毫秒为单位 |
timeScale | number | 增量倍数(不影响生成的补间动画) |
paused | boolean | 时间线是否已暂停 |
complete | boolean | 是否所有事件均已运行 |
loop | number | 额外循环次数(0=不循环,-1=无限) |
iteration | number | 当前循环迭代 |
totalComplete | number | 已触发事件的数量 |
events | TimelineEvent[] | 内部事件数组 |
注意事项
- repeat 与总触发次数。
repeat: 4表示总共触发 5 次回调(1 次初始 + 4 次重复)。这是常见的差一错误来源。 - repeat 配合零延迟会抛出异常。 带有
delay: 0且任意 repeat/loop 的TimerEvent会抛出'TimerEvent infinite loop created via zero delay'。 - Timeline 创建后处于暂停状态。 创建后必须调用
timeline.play()。忘记调用是常见错误。 - Timeline 的 timeScale 不影响 tween。 设置
timeline.timeScale只会缩放 Timeline 自身的已用时间计数器。由该 Timeline 创建的 tween 会按自己的速度运行。请单独设置 tween 的timeScale,或使用 TweenManager。 - once 事件会被永久移除。 带有
once: true的 Timeline 事件在触发后会被移除,不会在reset()或循环时重新出现。 - Timer 添加会被延迟。
addEvent()会将事件推入待处理列表,并在preUpdate中处理。Timer 要到下一帧才会生效。 - Clock 的 paused 与 timeScale 0。 两者都会冻结 Timer,但
paused = true会完全跳过更新循环,而timeScale = 0仍会以零增量运行循环。若要完全冻结,优先使用paused。 - callbackScope 默认值。 如果省略
callbackScope,回调内部的this将是 TimerEvent 本身,而不是 scene。请使用箭头函数,或显式传入callbackScope: this。 - 复用 TimerEvent 实例。 可以将
TimerEvent对象传给addEvent(),但它不能处于已完成状态。Clock 会重置其 elapsed 和 dispatch 状态。 - Timeline 的
from链式关系。 每个from偏移量都相对于前一个事件解析后的时间,而不是前一个from的值。事件按数组顺序处理。 - Scene 暂停会暂停 Timeline。 当 scene 被暂停(
scene.pause())时,Timeline 的更新循环也会停止,因为 Timeline 的更新由 scene 的 update 步骤驱动。 timer.reset()不会重新添加到 Clock。 调用timer.reset(config)会重新初始化 Timer,但不会调度它。重置后必须再次调用this.time.addEvent(timer)。
源文件映射
| 文件 | 描述 |
|---|---|
src/time/Clock.js | Scene Clock 插件 —— 创建、更新、移除 TimerEvent |
src/time/TimerEvent.js | 单个 Timer —— 延迟、repeat、loop、进度跟踪 |
src/time/Timeline.js | 事件序列器 —— 计划动作、tween、声音、循环 |
src/time/typedefs/TimerEventConfig.js | addEvent() 的配置类型定义 |
src/time/typedefs/TimelineEventConfig.js | timeline.add() 的配置类型定义 |
src/time/typedefs/TimelineEvent.js | 内部事件对象类型定义 |
src/time/events/COMPLETE_EVENT.js | Timeline 的 'complete' 事件常量 |
src/time/events/index.js | 事件命名空间的 barrel 文件 |