phaser-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phaser 4 Animations and Tweens

Phaser 4 动画与Tween

Phaser 4 has two distinct animation systems: frame-based sprite animations (flip through frames in a texture atlas or spritesheet) and tweens (interpolate numeric properties over time). Use both together for polished game feel.
Phaser 4 拥有两种不同的动画系统:基于帧的Sprite动画(在纹理图集或精灵表中切换帧)和Tween(随时间插值数值属性)。结合使用这两种系统可打造更精致的游戏体验。

Creating Spritesheet Animations

创建精灵表动画

A spritesheet packs multiple frames into a single image in a regular grid. Define animations in
AnimationManager
using frame indices.
typescript
// preload()
preload(): void {
  this.load.spritesheet('player', 'assets/player.png', {
    frameWidth: 48,
    frameHeight: 48,
  });
}

// create() — or PreloaderScene.create() for global animations (see below)
create(): void {
  this.anims.create({
    key:       'player-idle',
    frames:    this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
    frameRate: 8,
    repeat:    -1,           // -1 = loop forever
  });

  this.anims.create({
    key:       'player-walk',
    frames:    this.anims.generateFrameNumbers('player', { start: 4, end: 11 }),
    frameRate: 12,
    repeat:    -1,
  });

  this.anims.create({
    key:       'player-jump',
    frames:    this.anims.generateFrameNumbers('player', { start: 12, end: 15 }),
    frameRate: 10,
    repeat:    0,            // 0 = play once
  });

  this.anims.create({
    key:       'player-attack',
    frames:    this.anims.generateFrameNumbers('player', { start: 16, end: 23 }),
    frameRate: 16,
    repeat:    0,
  });
}
精灵表将多帧图像打包成一张规则网格的图片。使用帧索引在
AnimationManager
中定义动画。
typescript
// preload()
preload(): void {
  this.load.spritesheet('player', 'assets/player.png', {
    frameWidth: 48,
    frameHeight: 48,
  });
}

// create() — 或在PreloaderScene.create()中定义全局动画(见下文)
create(): void {
  this.anims.create({
    key:       'player-idle',
    frames:    this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
    frameRate: 8,
    repeat:    -1,           // -1 = 无限循环
  });

  this.anims.create({
    key:       'player-walk',
    frames:    this.anims.generateFrameNumbers('player', { start: 4, end: 11 }),
    frameRate: 12,
    repeat:    -1,
  });

  this.anims.create({
    key:       'player-jump',
    frames:    this.anims.generateFrameNumbers('player', { start: 12, end: 15 }),
    frameRate: 10,
    repeat:    0,            // 0 = 播放一次
  });

  this.anims.create({
    key:       'player-attack',
    frames:    this.anims.generateFrameNumbers('player', { start: 16, end: 23 }),
    frameRate: 16,
    repeat:    0,
  });
}

generateFrameNumbers Options

generateFrameNumbers 选项

typescript
this.anims.generateFrameNumbers('texture', {
  start:  0,          // first frame index
  end:    7,          // last frame index (inclusive)
  first:  0,          // override which frame plays first
  frames: [0, 2, 4],  // manual frame list (use instead of start/end)
});
typescript
this.anims.generateFrameNumbers('texture', {
  start:  0,          // 起始帧索引
  end:    7,          // 结束帧索引(包含)
  first:  0,          // 覆盖第一播放帧
  frames: [0, 2, 4],  // 手动指定帧列表(替代start/end)
});

Atlas-Based Animations

基于图集的动画

Texture atlases store frames with named keys rather than grid positions. Use
generateFrameNames
for these:
typescript
// preload()
this.load.atlas('hero', 'assets/hero.png', 'assets/hero.json');

// create()
this.anims.create({
  key:       'hero-run',
  frames:    this.anims.generateFrameNames('hero', {
    prefix:  'run_',    // frame names are run_01, run_02, ...
    start:   1,
    end:     8,
    zeroPad: 2,         // zero-pad the number to 2 digits
    suffix:  '',        // optional suffix after the number
  }),
  frameRate: 12,
  repeat:    -1,
});

// Manual frame list from atlas
this.anims.create({
  key:    'hero-die',
  frames: [
    { key: 'hero', frame: 'die_01' },
    { key: 'hero', frame: 'die_02' },
    { key: 'hero', frame: 'die_03' },
  ],
  frameRate: 8,
  repeat:    0,
});
纹理图集使用命名键而非网格位置存储帧。对此类图集使用
generateFrameNames
typescript
// preload()
this.load.atlas('hero', 'assets/hero.png', 'assets/hero.json');

// create()
this.anims.create({
  key:       'hero-run',
  frames:    this.anims.generateFrameNames('hero', {
    prefix:  'run_',    // 帧名称格式为run_01, run_02, ...
    start:   1,
    end:     8,
    zeroPad: 2,         // 数字补零至2位
    suffix:  '',        // 数字后的可选后缀
  }),
  frameRate: 12,
  repeat:    -1,
});

// 从图集中手动指定帧列表
this.anims.create({
  key:    'hero-die',
  frames: [
    { key: 'hero', frame: 'die_01' },
    { key: 'hero', frame: 'die_02' },
    { key: 'hero', frame: 'die_03' },
  ],
  frameRate: 8,
  repeat:    0,
});

Where to Define Animations

动画定义位置

Define animations in
PreloaderScene.create()
— not in each individual scene.
Animations registered on the global
AnimationManager
are available in every scene without re-registering:
typescript
// src/scenes/PreloaderScene.ts
export class PreloaderScene extends Phaser.Scene {
  preload(): void {
    this.load.spritesheet('player', 'assets/player.png', { frameWidth: 48, frameHeight: 48 });
    this.load.atlas('enemies', 'assets/enemies.png', 'assets/enemies.json');
  }

  create(): void {
    // All anims defined here are available in GameScene, UIScene, etc.
    this.anims.create({ key: 'player-idle', /* ... */ });
    this.anims.create({ key: 'player-walk', /* ... */ });
    this.anims.create({ key: 'enemy-walk',  /* ... */ });

    this.scene.start('GameScene');
  }
}
If an animation only makes sense in a single scene (a cutscene animation, for example), define it in that scene's
create()
.
PreloaderScene.create()
中定义动画——而非在每个单独场景中。
在全局
AnimationManager
中注册的动画可在所有场景中使用,无需重新注册:
typescript
// src/scenes/PreloaderScene.ts
export class PreloaderScene extends Phaser.Scene {
  preload(): void {
    this.load.spritesheet('player', 'assets/player.png', { frameWidth: 48, frameHeight: 48 });
    this.load.atlas('enemies', 'assets/enemies.png', 'assets/enemies.json');
  }

  create(): void {
    // 此处定义的所有动画可在GameScene、UIScene等场景中使用
    this.anims.create({ key: 'player-idle', /* ... */ });
    this.anims.create({ key: 'player-walk', /* ... */ });
    this.anims.create({ key: 'enemy-walk',  /* ... */ });

    this.scene.start('GameScene');
  }
}
如果某个动画仅适用于单个场景(例如过场动画),则在该场景的
create()
中定义。

Playing Animations

播放动画

typescript
// Basic play
sprite.play('player-walk');

// Play but don't restart if already playing this animation
sprite.play('player-walk', true);    // ignoreIfPlaying = true

// Play starting from a specific frame
sprite.playFromFrame('player-walk', 3);

// Stop on a specific frame number
sprite.stopOnFrame(this.anims.get('player-attack').frames[7]);

// Reverse playback
sprite.playReverse('player-walk');

// Check state
sprite.anims.isPlaying;                    // boolean
sprite.anims.currentAnim?.key;             // string | undefined
sprite.anims.currentFrame?.index;          // current frame index
typescript
// 基础播放
sprite.play('player-walk');

// 若已在播放该动画则不重启
sprite.play('player-walk', true);    // ignoreIfPlaying = true

// 从指定帧开始播放
sprite.playFromFrame('player-walk', 3);

// 在指定帧停止
sprite.stopOnFrame(this.anims.get('player-attack').frames[7]);

// 反向播放
sprite.playReverse('player-walk');

// 检查状态
sprite.anims.isPlaying;                    // 布尔值
sprite.anims.currentAnim?.key;             // 字符串 | undefined
sprite.anims.currentFrame?.index;          // 当前帧索引

Animation Events

动画事件

Listen for animation lifecycle events on the sprite (not the AnimationManager):
typescript
// Fires when any animation completes on this sprite
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (anim, frame, gameObject) => {
  console.log('animation complete:', anim.key);
});

// Fires when a SPECIFIC animation completes (preferred — avoids key checks)
sprite.on(
  Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-attack',
  (anim, frame, gameObject) => {
    this.player.returnToIdle();
  }
);

// Other events
sprite.on(Phaser.Animations.Events.ANIMATION_START,   cb);  // animation started
sprite.on(Phaser.Animations.Events.ANIMATION_REPEAT,  cb);  // loop restarted
sprite.on(Phaser.Animations.Events.ANIMATION_RESTART, cb);  // play() called while already playing
sprite.on(Phaser.Animations.Events.ANIMATION_STOP,    cb);  // stop() called
sprite.on(Phaser.Animations.Events.ANIMATION_UPDATE,  cb);  // every frame change
Always remove listeners when the sprite is destroyed to prevent memory leaks:
typescript
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
// In shutdown():
sprite.off(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
Sprite上监听动画生命周期事件(而非AnimationManager):
typescript
// 当该Sprite上的任意动画完成时触发
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (anim, frame, gameObject) => {
  console.log('动画完成:', anim.key);
});

// 当特定动画完成时触发(推荐使用——无需检查键值)
sprite.on(
  Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-attack',
  (anim, frame, gameObject) => {
    this.player.returnToIdle();
  }
);

// 其他事件
sprite.on(Phaser.Animations.Events.ANIMATION_START,   cb);  // 动画开始
sprite.on(Phaser.Animations.Events.ANIMATION_REPEAT,  cb);  // 循环重启
sprite.on(Phaser.Animations.Events.ANIMATION_RESTART, cb);  // 播放时已在播放该动画
sprite.on(Phaser.Animations.Events.ANIMATION_STOP,    cb);  // 停止动画
sprite.on(Phaser.Animations.Events.ANIMATION_UPDATE,  cb);  // 每帧切换
销毁Sprite时务必移除监听器,以防止内存泄漏:
typescript
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
// 在shutdown()中:
sprite.off(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);

Animation Chaining

动画链式播放

Play a sequence of animations one after another:
typescript
// Chain via array — plays 'attack', then 'idle' automatically
sprite.chain(['player-attack', 'player-idle']);
sprite.play('player-attack');

// Chain via ANIMATION_COMPLETE event
sprite.play('player-jump');
sprite.once(
  Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-jump',
  () => sprite.play('player-fall')
);
依次播放一系列动画:
typescript
// 通过数组链式播放——先播放'attack',然后自动播放'idle'
sprite.chain(['player-attack', 'player-idle']);
sprite.play('player-attack');

// 通过ANIMATION_COMPLETE事件链式播放
sprite.play('player-jump');
sprite.once(
  Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-jump',
  () => sprite.play('player-fall')
);

Character State Machine Pattern

角色状态机模式

For characters with idle/walk/jump/attack states, use an explicit state machine in
update()
. This prevents impossible state transitions and makes animation logic readable.
typescript
type CharState = 'idle' | 'walk' | 'jump' | 'attack' | 'hurt';

export class Player extends Phaser.Physics.Arcade.Sprite {
  private state: CharState = 'idle';

  setState(newState: CharState): void {
    if (this.state === newState) return;
    this.state = newState;
    switch (newState) {
      case 'idle':   this.play('player-idle',   true); break;
      case 'walk':   this.play('player-walk',   true); break;
      case 'jump':   this.play('player-jump',   true); break;
      case 'attack': this.play('player-attack', true); break;
      case 'hurt':
        this.play('player-hurt', true);
        this.once(
          Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-hurt',
          () => this.setState('idle')
        );
        break;
    }
  }

  update(cursors: Phaser.Types.Input.Keyboard.CursorKeys): void {
    const body = this.body as Phaser.Physics.Arcade.Body;

    if (this.state === 'attack' || this.state === 'hurt') return;  // locked states

    if (!body.blocked.down) {
      this.setState('jump');
    } else if (cursors.left.isDown || cursors.right.isDown) {
      this.setState('walk');
    } else {
      this.setState('idle');
    }

    if (Phaser.Input.Keyboard.JustDown(cursors.space)) {
      this.setState('attack');
      this.once(
        Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-attack',
        () => this.setState('idle')
      );
    }
  }
}
对于拥有Idle/行走/跳跃/攻击状态的角色,在
update()
中使用显式状态机。这可防止不可能的状态转换,并使动画逻辑更易读。
typescript
type CharState = 'idle' | 'walk' | 'jump' | 'attack' | 'hurt';

export class Player extends Phaser.Physics.Arcade.Sprite {
  private state: CharState = 'idle';

  setState(newState: CharState): void {
    if (this.state === newState) return;
    this.state = newState;
    switch (newState) {
      case 'idle':   this.play('player-idle',   true); break;
      case 'walk':   this.play('player-walk',   true); break;
      case 'jump':   this.play('player-jump',   true); break;
      case 'attack': this.play('player-attack', true); break;
      case 'hurt':
        this.play('player-hurt', true);
        this.once(
          Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-hurt',
          () => this.setState('idle')
        );
        break;
    }
  }

  update(cursors: Phaser.Types.Input.Keyboard.CursorKeys): void {
    const body = this.body as Phaser.Physics.Arcade.Body;

    if (this.state === 'attack' || this.state === 'hurt') return;  // 锁定状态

    if (!body.blocked.down) {
      this.setState('jump');
    } else if (cursors.left.isDown || cursors.right.isDown) {
      this.setState('walk');
    } else {
      this.setState('idle');
    }

    if (Phaser.Input.Keyboard.JustDown(cursors.space)) {
      this.setState('attack');
      this.once(
        Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + 'player-attack',
        () => this.setState('idle')
      );
    }
  }
}

Forced Animations: Cinematic Mode

强制动画:过场模式

When a one-shot animation (boss intro, death sequence, dungeon entry) plays for one frame then reverts to idle, the cause is always the entity's
update()
running its state-machine logic one tick after your forced
play()
call and overwriting it.
Fix — add a
cinematicMode
flag as the very first guard in
update()
:
typescript
export class Player extends Phaser.Physics.Arcade.Sprite {
  private state: CharState = 'idle';
  private cinematicMode = false;

  setCinematicMode(active: boolean, forcedAnimKey?: string): void {
    this.cinematicMode = active;
    if (active && forcedAnimKey) {
      this.anims.stop();          // RC7: always stop before play on state switch
      this.play(forcedAnimKey, true);
      this.once(
        Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + forcedAnimKey,
        () => { this.cinematicMode = false; }
      );
    }
  }

  update(cursors: Phaser.Types.Input.Keyboard.CursorKeys): void {
    if (this.cinematicMode) return;  // MUST be first line — blocks state logic
    // ... rest of state machine
  }
}
Clear
cinematicMode
in the
ANIMATION_COMPLETE_KEY
handler, not synchronously after
play()
— in RC7 the completion event fires one tick after the last frame renders. Clear it synchronously and your forced animation exits one frame early.
See
references/state-machine-patterns.md
for the full canonical implementation, worked dungeon-entry example, and the RC7
ANIMATION_COMPLETE
timing-drift fix.
当单次动画(Boss登场、死亡序列、进入地牢)播放一帧后就恢复为Idle状态,原因总是实体的
update()
在你调用强制
play()
的下一帧运行状态机逻辑并覆盖了它。
修复方案——在
update()
的最开头添加
cinematicMode
标志作为防护:
typescript
export class Player extends Phaser.Physics.Arcade.Sprite {
  private state: CharState = 'idle';
  private cinematicMode = false;

  setCinematicMode(active: boolean, forcedAnimKey?: string): void {
    this.cinematicMode = active;
    if (active && forcedAnimKey) {
      this.anims.stop();          // RC7:状态切换时务必先停止再播放
      this.play(forcedAnimKey, true);
      this.once(
        Phaser.Animations.Events.ANIMATION_COMPLETE_KEY + forcedAnimKey,
        () => { this.cinematicMode = false; }
      );
    }
  }

  update(cursors: Phaser.Types.Input.Keyboard.CursorKeys): void {
    if (this.cinematicMode) return;  // 必须是第一行——阻止状态逻辑
    // ... 状态机剩余代码
  }
}
ANIMATION_COMPLETE_KEY
处理程序中清除
cinematicMode
,而非在
play()
后同步清除——在RC7中,完成事件会在最后一帧渲染后的下一帧触发。同步清除会导致强制动画提前一帧结束。
请参阅
references/state-machine-patterns.md
获取完整的标准实现、地牢进入示例以及RC7中
ANIMATION_COMPLETE
时序偏移的修复方案。

State Transition Completeness

状态转换完整性

Adding a new animation state without auditing all other states' transition lists is a silent bug — no error is thrown; the state machine simply fails to reach the new state or gets stuck in the wrong one.
When adding any new state (e.g.
'dodge'
,
'interact'
):
  1. Add it to the
    CharState
    union type.
  2. Add a
    case
    for it in
    setState()
    .
  3. Update every other state's "what can interrupt me" logic to include or exclude the new state as appropriate.
The transition table in
references/state-machine-patterns.md
makes missing transitions obvious on read.
添加新动画状态而不检查所有其他状态的转换列表会导致隐性错误——不会抛出错误;状态机只是无法到达新状态或卡在错误状态中。
添加任何新状态(例如
'dodge'
'interact'
)时:
  1. 将其添加到
    CharState
    联合类型中。
  2. setState()
    中添加对应的
    case
    分支。
  3. 更新所有其他状态的“哪些状态可以中断我”逻辑,根据情况包含或排除新状态。
references/state-machine-patterns.md
中的转换表可让你一眼看出缺失的转换。

Stopping and Pausing Animations

停止与暂停动画

typescript
sprite.stop();            // stop and stay on current frame
sprite.anims.pause();     // pause on current frame (resumable)
sprite.anims.resume();    // resume paused animation
sprite.anims.restart();   // restart from frame 0
typescript
sprite.stop();            // 停止并停留在当前帧
sprite.anims.pause();     // 暂停在当前帧(可恢复)
sprite.anims.resume();    // 恢复暂停的动画
sprite.anims.restart();   // 从第0帧重启

Tweens

Tween

Tweens interpolate any numeric property on any object over time. They are Phaser's primary tool for UI animations, cutscenes, and visual feedback.
typescript
this.tweens.add({
  targets:    sprite,       // one object, an array, or a group
  x:          400,          // tween x to 400
  y:          300,
  alpha:      1,
  duration:   800,          // milliseconds
  ease:       'Quad.Out',   // easing function
  delay:      0,            // ms before starting
  repeat:     0,            // 0 = once; -1 = infinite
  yoyo:       false,        // reverse back to start after completing
  hold:       0,            // ms to hold at end before yoyo
  onStart:    () => {},     // fires when tween starts
  onUpdate:   () => {},     // fires every frame
  onComplete: () => {},     // fires on completion
});
Tween可随时间插值任意对象的数值属性。它们是Phaser用于UI动画、过场动画和视觉反馈的主要工具。
typescript
this.tweens.add({
  targets:    sprite,       // 单个对象、数组或组
  x:          400,          // 将x插值到400
  y:          300,
  alpha:      1,
  duration:   800,          // 毫秒
  ease:       'Quad.Out',   // 缓动函数
  delay:      0,            // 启动前延迟毫秒数
  repeat:     0,            // 0 = 播放一次;-1 = 无限循环
  yoyo:       false,        // 完成后反向回到起始状态
  hold:       0,            // 结束后保持状态的毫秒数
  onStart:    () => {},     // 动画启动时触发
  onUpdate:   () => {},     // 每帧触发
  onComplete: () => {},     // 完成时触发
});

Common Tween Patterns

常见Tween模式

Fade In

淡入

typescript
sprite.setAlpha(0);
this.tweens.add({ targets: sprite, alpha: 1, duration: 400, ease: 'Linear' });
typescript
sprite.setAlpha(0);
this.tweens.add({ targets: sprite, alpha: 1, duration: 400, ease: 'Linear' });

Fade Out and Destroy

淡出并销毁

typescript
this.tweens.add({
  targets:    sprite,
  alpha:      0,
  duration:   300,
  ease:       'Linear',
  onComplete: () => sprite.destroy(),
});
typescript
this.tweens.add({
  targets:    sprite,
  alpha:      0,
  duration:   300,
  ease:       'Linear',
  onComplete: () => sprite.destroy(),
});

Scale Pulse (hit feedback, collectible)

缩放脉冲(击中反馈、收集物)

typescript
this.tweens.add({
  targets:  sprite,
  scaleX:   1.3,
  scaleY:   1.3,
  duration: 80,
  ease:     'Quad.Out',
  yoyo:     true,
});
typescript
this.tweens.add({
  targets:  sprite,
  scaleX:   1.3,
  scaleY:   1.3,
  duration: 80,
  ease:     'Quad.Out',
  yoyo:     true,
});

Slide In From Edge

从边缘滑入

typescript
// Slide in from left
sprite.setX(-100);
this.tweens.add({
  targets:  sprite,
  x:        400,
  duration: 500,
  ease:     'Back.Out',
});
typescript
// 从左侧滑入
sprite.setX(-100);
this.tweens.add({
  targets:  sprite,
  x:        400,
  duration: 500,
  ease:     'Back.Out',
});

Bounce Landing

弹跳落地

typescript
sprite.setY(targetY - 100);
this.tweens.add({
  targets:  sprite,
  y:        targetY,
  duration: 600,
  ease:     'Bounce.Out',
});
typescript
sprite.setY(targetY - 100);
this.tweens.add({
  targets:  sprite,
  y:        targetY,
  duration: 600,
  ease:     'Bounce.Out',
});

Tween Easing Functions

Tween缓动函数

See
references/easing-reference.md
for the complete guide with all easing functions and use cases.
Quick reference:
  • 'Linear'
    — constant speed; mechanical, UI bars
  • 'Quad.Out'
    — fast start, decelerates; most natural movement
  • 'Quad.In'
    — accelerates; falling objects, winding up
  • 'Quad.InOut'
    — symmetric ease; camera moves
  • 'Back.Out'
    — overshoots target then settles; UI popups, dialog slides
  • 'Bounce.Out'
    — bounces at destination; objects hitting ground
  • 'Elastic.Out'
    — spring oscillation; comic, bouncy UI
完整的缓动函数指南及使用场景请参阅
references/easing-reference.md
快速参考:
  • 'Linear'
    — 匀速;机械感,适用于UI进度条
  • 'Quad.Out'
    — 快启动,减速;最自然的运动效果
  • 'Quad.In'
    — 加速;下落物体、蓄力动作
  • 'Quad.InOut'
    — 对称缓动;相机移动
  • 'Back.Out'
    — 超过目标后回落;UI弹窗、对话框滑入
  • 'Bounce.Out'
    — 在目标处弹跳;物体落地
  • 'Elastic.Out'
    — 弹簧振荡;漫画风格、弹跳UI

Tween Timelines

Tween时间线

Sequence multiple tweens without nesting
onComplete
callbacks:
typescript
this.tweens.timeline({
  tweens: [
    {
      targets:  panel,
      alpha:    1,
      duration: 200,
    },
    {
      targets:  panel,
      y:        300,
      duration: 400,
      ease:     'Back.Out',
    },
    {
      targets:  title,
      alpha:    1,
      duration: 300,
      offset:   '-=100',   // start 100ms before previous tween ends (overlap)
    },
    {
      targets:  button,
      alpha:    1,
      duration: 200,
      // no offset = starts after previous completes
    },
  ],
});
offset
controls timing relative to the previous tween:
  • '-=200'
    — overlap by 200ms
  • '+=200'
    — add 200ms gap
  • absolute number — start at that ms from timeline start
无需嵌套
onComplete
回调即可按顺序播放多个Tween:
typescript
this.tweens.timeline({
  tweens: [
    {
      targets:  panel,
      alpha:    1,
      duration: 200,
    },
    {
      targets:  panel,
      y:        300,
      duration: 400,
      ease:     'Back.Out',
    },
    {
      targets:  title,
      alpha:    1,
      duration: 300,
      offset:   '-=100',   // 在前一个Tween结束前100ms启动(重叠)
    },
    {
      targets:  button,
      alpha:    1,
      duration: 200,
      // 无offset = 在前一个Tween完成后启动
    },
  ],
});
offset
控制相对于前一个Tween的时序:
  • '-=200'
    — 重叠200ms
  • '+=200'
    — 添加200ms间隔
  • 绝对数字 — 从时间线启动后指定毫秒数开始

Particle Animations (Brief)

粒子动画(简介)

For burst effects (explosions, pickups, impacts), use the built-in particle system:
typescript
// One-shot burst
this.add.particles(x, y, 'spark', {
  speed:     { min: 50, max: 200 },
  angle:     { min: 0, max: 360 },
  scale:     { start: 1, end: 0 },
  lifespan:  600,
  quantity:  12,
  emitting:  false,         // don't start automatically
}).explode(12);             // emit 12 particles immediately then stop

// Persistent emitter (fire, rain)
const emitter = this.add.particles(x, y, 'flame', {
  speed:    30,
  lifespan: 1200,
  scale:    { start: 0.8, end: 0 },
  alpha:    { start: 1, end: 0 },
  frequency: 80,            // ms between emissions
});
// Stop later:
emitter.stop();
对于爆发效果(爆炸、收集物、撞击),使用内置粒子系统:
typescript
// 单次爆发
this.add.particles(x, y, 'spark', {
  speed:     { min: 50, max: 200 },
  angle:     { min: 0, max: 360 },
  scale:     { start: 1, end: 0 },
  lifespan:  600,
  quantity:  12,
  emitting:  false,         // 不自动启动
}).explode(12);             // 立即发射12个粒子然后停止

// 持续发射器(火焰、雨水)
const emitter = this.add.particles(x, y, 'flame', {
  speed:    30,
  lifespan: 1200,
  scale:    { start: 0.8, end: 0 },
  alpha:    { start: 1, end: 0 },
  frequency: 80,            // 发射间隔毫秒数
});
// 后续停止:
emitter.stop();

Additional Resources

额外资源

Reference Files

参考文件

  • references/animation-api.md
    — Complete AnimationManager, AnimationConfig, Animation events, TweenManager, and Timeline API reference
  • references/easing-reference.md
    — All built-in easing functions with descriptions, use cases, and code examples
  • references/state-machine-patterns.md
    — State-machine discipline for characters:
    cinematicMode
    flag, canonical state list, transition table, RC7
    stop()
    /
    play()
    ordering rule, and
    ANIMATION_COMPLETE
    timing drift. Read when building any character with more than idle+walk, or when forced animations play for one frame and revert.
  • references/animation-api.md
    — 完整的AnimationManager、AnimationConfig、动画事件、TweenManager和时间线API参考
  • references/easing-reference.md
    — 所有内置缓动函数的说明、使用场景和代码示例
  • references/state-machine-patterns.md
    — 角色状态机规范:
    cinematicMode
    标志、标准状态列表、转换表、RC7中
    stop()
    /
    play()
    的调用顺序规则以及
    ANIMATION_COMPLETE
    时序偏移修复。当构建拥有Idle+行走之外更多状态的角色,或遇到强制动画仅播放一帧就恢复的问题时,请阅读此文档。