phaser-animation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhaser 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 using frame indices.
AnimationManagertypescript
// 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,
});
}精灵表将多帧图像打包成一张规则网格的图片。使用帧索引在中定义动画。
AnimationManagertypescript
// 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 for these:
generateFrameNamestypescript
// 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,
});纹理图集使用命名键而非网格位置存储帧。对此类图集使用:
generateFrameNamestypescript
// 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 — not in each individual scene. Animations registered on the global are available in every scene without re-registering:
PreloaderScene.create()AnimationManagertypescript
// 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()AnimationManagertypescript
// 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 indextypescript
// 基础播放
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 changeAlways 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 . This prevents impossible state transitions and makes animation logic readable.
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; // 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 running its state-machine logic one tick after your forced call and overwriting it.
update()play()Fix — add a flag as the very first guard in :
cinematicModeupdate()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 in the handler, not synchronously after — in RC7 the completion event fires one tick after the last frame renders. Clear it synchronously and your forced animation exits one frame early.
cinematicModeANIMATION_COMPLETE_KEYplay()See for the full canonical implementation, worked dungeon-entry example, and the RC7 timing-drift fix.
references/state-machine-patterns.mdANIMATION_COMPLETE当单次动画(Boss登场、死亡序列、进入地牢)播放一帧后就恢复为Idle状态,原因总是实体的在你调用强制的下一帧运行状态机逻辑并覆盖了它。
update()play()修复方案——在的最开头添加标志作为防护:
update()cinematicModetypescript
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; // 必须是第一行——阻止状态逻辑
// ... 状态机剩余代码
}
}在处理程序中清除,而非在后同步清除——在RC7中,完成事件会在最后一帧渲染后的下一帧触发。同步清除会导致强制动画提前一帧结束。
ANIMATION_COMPLETE_KEYcinematicModeplay()请参阅获取完整的标准实现、地牢进入示例以及RC7中时序偏移的修复方案。
references/state-machine-patterns.mdANIMATION_COMPLETEState 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'- Add it to the union type.
CharState - Add a for it in
case.setState() - Update every other state's "what can interrupt me" logic to include or exclude the new state as appropriate.
The transition table in makes missing transitions obvious on read.
references/state-machine-patterns.md添加新动画状态而不检查所有其他状态的转换列表会导致隐性错误——不会抛出错误;状态机只是无法到达新状态或卡在错误状态中。
添加任何新状态(例如、)时:
'dodge''interact'- 将其添加到联合类型中。
CharState - 在中添加对应的
setState()分支。case - 更新所有其他状态的“哪些状态可以中断我”逻辑,根据情况包含或排除新状态。
references/state-machine-patterns.mdStopping 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 0typescript
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 for the complete guide with all easing functions and use cases.
references/easing-reference.mdQuick reference:
- — constant speed; mechanical, UI bars
'Linear' - — fast start, decelerates; most natural movement
'Quad.Out' - — accelerates; falling objects, winding up
'Quad.In' - — symmetric ease; camera moves
'Quad.InOut' - — overshoots target then settles; UI popups, dialog slides
'Back.Out' - — bounces at destination; objects hitting ground
'Bounce.Out' - — spring oscillation; comic, bouncy UI
'Elastic.Out'
完整的缓动函数指南及使用场景请参阅。
references/easing-reference.md快速参考:
- — 匀速;机械感,适用于UI进度条
'Linear' - — 快启动,减速;最自然的运动效果
'Quad.Out' - — 加速;下落物体、蓄力动作
'Quad.In' - — 对称缓动;相机移动
'Quad.InOut' - — 超过目标后回落;UI弹窗、对话框滑入
'Back.Out' - — 在目标处弹跳;物体落地
'Bounce.Out' - — 弹簧振荡;漫画风格、弹跳UI
'Elastic.Out'
Tween Timelines
Tween时间线
Sequence multiple tweens without nesting callbacks:
onCompletetypescript
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- — overlap by 200ms
'-=200' - — add 200ms gap
'+=200' - absolute number — start at that ms from timeline start
无需嵌套回调即可按顺序播放多个Tween:
onCompletetypescript
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- — 重叠200ms
'-=200' - — 添加200ms间隔
'+=200' - 绝对数字 — 从时间线启动后指定毫秒数开始
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
参考文件
- — Complete AnimationManager, AnimationConfig, Animation events, TweenManager, and Timeline API reference
references/animation-api.md - — All built-in easing functions with descriptions, use cases, and code examples
references/easing-reference.md - — State-machine discipline for characters:
references/state-machine-patterns.mdflag, canonical state list, transition table, RC7cinematicMode/stop()ordering rule, andplay()timing drift. Read when building any character with more than idle+walk, or when forced animations play for one frame and revert.ANIMATION_COMPLETE
- — 完整的AnimationManager、AnimationConfig、动画事件、TweenManager和时间线API参考
references/animation-api.md - — 所有内置缓动函数的说明、使用场景和代码示例
references/easing-reference.md - — 角色状态机规范:
references/state-machine-patterns.md标志、标准状态列表、转换表、RC7中cinematicMode/stop()的调用顺序规则以及play()时序偏移修复。当构建拥有Idle+行走之外更多状态的角色,或遇到强制动画仅播放一帧就恢复的问题时,请阅读此文档。ANIMATION_COMPLETE