关于 animations
面向 Phaser 4 开发者的 Sprite 动画技能参考,介绍动画定义与播放控制,涵盖精灵表、纹理图集、事件和逐帧回调。
主要功能
- 01全局与 Sprite 局部动画定义
- 02精灵表与图集帧生成
- 03播放、反向、暂停和串联控制
- 04动画事件与逐帧回调
- 05JSON 与 Aseprite 动画导入
适用场景
- 01创建循环角色动画
- 02串联攻击、坠落和着陆动画
- 03在特定帧触发游戏逻辑
- 04导入 Aseprite 动画标签
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill animations安装后在 Claude Code 中输入/animations确认已加载。
详细介绍
Phaser 4 -- 精灵动画
AnimationManager(全局)、AnimationState(每个精灵)、从 spritesheet 和 atlas 创建动画、播放/暂停/链接、动画事件、帧回调。
相关技能: ../sprites-and-images/SKILL.md、../loading-assets/SKILL.md
快速开始
// In preload -- load a spritesheet
this.load.spritesheet('explosion', 'explosion.png', {
frameWidth: 64,
frameHeight: 64
});
// In create -- define a global animation
this.anims.create({
key: 'explode',
frames: this.anims.generateFrameNumbers('explosion', { start: 0, end: 11 }),
frameRate: 24,
repeat: 0
});
// Play it on a sprite
const sprite = this.add.sprite(400, 300, 'explosion');
sprite.play('explode');
核心概念
AnimationManager 与 AnimationState
Phaser 有两个不同的动画对象:
| 方面 | AnimationManager | AnimationState |
|---|---|---|
| 访问 | this.anims(在 Scene 中)或 this.game.anims | sprite.anims |
| 范围 | 全局——在所有场景间共享 | 每个精灵实例 |
| 用途 | 创建/存储动画定义 | 控制单个 Game Object 上的播放 |
| 类 | Phaser.Animations.AnimationManager | Phaser.Animations.AnimationState |
AnimationManager 是由 Game 拥有的单例。在那里注册的动画在每个 Scene 中都可用。AnimationState 存在于每个 Sprite 上,并处理该特定对象的播放。
一个 Animation 是一系列 AnimationFrame 对象加上时间数据。通过 this.anims.create(config)(全局)或 sprite.anims.create(config)(局部于一个精灵)创建。
局部动画与全局动画
当调用 sprite.anims.play(key) 时,它首先检查具有该键的局部动画,然后回退到全局 AnimationManager。对于特定于精灵的动画,请使用局部;当在多个精灵间共享时,请使用全局。
// Global animation -- available to all sprites
this.anims.create({ key: 'walk', frames: 'player_walk', frameRate: 12, repeat: -1 });
// Local animation -- only on this sprite
sprite.anims.create({ key: 'walk', frames: 'npc_walk', frameRate: 10, repeat: -1 });
// This plays the LOCAL version because local takes priority
sprite.play('walk');
常见模式
Spritesheet 动画
对 spritesheet(数字帧索引)使用 generateFrameNumbers。
this.load.spritesheet('dude', 'dude.png', { frameWidth: 32, frameHeight: 48 });
// All frames
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 7 }),
frameRate: 10,
repeat: -1
});
// Custom frame sequence
this.anims.create({
key: 'idle',
frames: this.anims.generateFrameNumbers('dude', { frames: [0, 1, 2, 1] }),
frameRate: 6,
repeat: -1
});
generateFrameNumbers 配置:
start(默认0)——第一帧索引end(默认-1,表示最后一帧)——最终帧索引first——在范围之前前置的单个帧frames——明确的帧索引数组(覆盖 start/end)
Atlas 动画
对纹理 atlas(基于字符串的帧名称)使用 generateFrameNames。
this.load.atlas('gems', 'gems.png', 'gems.json');
this.anims.create({
key: 'ruby_sparkle',
frames: this.anims.generateFrameNames('gems', {
prefix: 'ruby_',
start: 1,
end: 6,
zeroPad: 4 // produces ruby_0001 through ruby_0006
}),
frameRate: 12,
repeat: -1
});
generateFrameNames 配置:
prefix——前置到每个帧编号suffix——附加在每个帧编号之后start、end——数字范围zeroPad——用零将数字左填充到此长度frames——明确的帧编号数组(覆盖 start/end)
如果在没有配置的情况下调用 generateFrameNames(key),它会返回 atlas 中的所有帧。
字符串作为帧
将纹理键字符串作为 frames 传入,以使用该纹理中的所有帧,默认按数字排序。设置 sortFrames: false 可禁用排序。
this.anims.create({ key: 'walk', frames: 'player_walk', frameRate: 12, repeat: -1 });
Yoyo 与 Repeat
this.anims.create({
key: 'pulse',
frames: this.anims.generateFrameNumbers('orb', { start: 0, end: 5 }),
frameRate: 10,
yoyo: true, // plays forward then backward
repeat: -1, // -1 = forever
repeatDelay: 500 // ms pause between each repeat cycle
});
当 yoyo 为 true 时,动画先向前播放然后反向播放。整个循环算作一次播放。
链接动画
sprite.play('attack');
sprite.chain('idle'); // play idle after attack completes
sprite.chain(['fall', 'land', 'idle']); // chain multiple
sprite.anims.chain(); // clear the chain queue
链接是按精灵的。链接的动画在 animationcomplete 或 animationstop 之后开始。具有 repeat: -1 的动画永远不会完成——调用 stop() 来触发链接。
反向播放
// Play an animation from last frame to first
sprite.playReverse('walk');
// Reverse direction mid-playback
sprite.anims.reverse();
playReverse 设置 forward = false 和 inReverse = true。reverse() 方法在播放中途切换方向。
播放变体
sprite.play('walk', true); // ignoreIfPlaying = true
sprite.anims.playAfterDelay('walk', 1000); // play after 1s delay
sprite.anims.playAfterRepeat('walk', 2); // play after current anim repeats 2x
动画混合
在两个特定动画之间添加过渡延迟,全局设置在 AnimationManager 上。
this.anims.addMix('idle', 'walk', 200);
this.anims.addMix('walk', 'idle', 300);
sprite.play('idle');
sprite.play('walk'); // 200ms mix delay applied automatically
this.anims.removeMix('idle', 'walk'); // remove specific pair
this.anims.removeMix('idle'); // remove all mixes for 'idle'
混合延迟仅适用于 sprite.play(),不适用于 playAfterDelay 或 playAfterRepeat。
暂停、恢复与停止
sprite.anims.pause(); // pause per-sprite
sprite.anims.resume(); // resume per-sprite
this.anims.pauseAll(); // global pause
this.anims.resumeAll(); // global resume
sprite.anims.stop(); // stop immediately
sprite.anims.stopAfterDelay(2000); // stop after 2 seconds
sprite.anims.stopAfterRepeat(1); // stop after 1 more repeat
sprite.anims.stopOnFrame(frame); // stop when a specific frame is reached
所有停止方法都会触发 animationstop(而不是 animationcomplete)。链接的动画在停止后触发。
动画事件
sprite.on('animationcomplete', (anim, frame, gameObject, frameKey) => {
console.log('completed:', anim.key);
});
// Key-specific complete -- only fires for the named animation
sprite.on('animationcomplete-explode', (anim, frame, gameObject, frameKey) => {
gameObject.destroy();
});
可用事件:animationstart、animationcomplete、animationcomplete-{key}、animationupdate、animationstop、animationrepeat、animationrestart。它们都共享相同的回调签名:(animation, frame, gameObject, frameKey)。
通过 animationupdate 的帧级回调
sprite.on('animationupdate', (anim, frame, gameObject, frameKey) => {
if (anim.key === 'attack' && frame.index === 4) {
this.checkHit(gameObject);
}
});
每帧持续时间
单独的帧可以有一个 duration(毫秒),它会被加到基础的 msPerFrame 上。
this.anims.create({
key: 'combo',
frames: [
{ key: 'fighter', frame: 'punch1', duration: 50 },
{ key: 'fighter', frame: 'kick', duration: 200 }, // hold longer
{ key: 'fighter', frame: 'recover', duration: 100 }
],
frameRate: 24
});
可见性、随机开始与 TimeScale
// Visibility control
this.anims.create({
key: 'appear', frames: 'sparkle', frameRate: 12,
showOnStart: true, // sprite.visible = true when anim starts (after delay)
hideOnComplete: true, // sprite.visible = false when anim completes
showBeforeDelay: true // show first frame immediately even during delay
});
// Random start frame -- each sprite begins on a different frame
this.anims.create({
key: 'ambient', frames: 'fire', frameRate: 10, repeat: -1,
randomFrame: true
});
// TimeScale -- per-sprite or global speed control
sprite.anims.timeScale = 2; // 2x speed
sprite.play({ key: 'walk', timeScale: 0.5 }); // half speed via config
this.anims.globalTimeScale = 0.5; // affects ALL animations
错开播放
const enemies = this.add.group({ key: 'enemy', repeat: 9 });
this.anims.staggerPlay('walk', enemies.getChildren(), 100);
// Each sprite starts 100ms after the previous. Pass staggerFirst: false to skip delay on first.
JSON 导出与导入
// Export all global animations to JSON
const data = this.anims.toJSON();
// Import animations from JSON (pass true to clear existing animations first)
this.anims.fromJSON(data);
this.anims.fromJSON(data, true);
// Check before creating to avoid duplicate warning
if (!this.anims.exists('walk')) {
this.anims.create({ key: 'walk', frames: 'player_walk', frameRate: 12, repeat: -1 });
}
运行时修改动画帧
const anim = this.anims.get('walk');
// Add frames to the end
anim.addFrame(this.anims.generateFrameNumbers('player', { start: 8, end: 10 }));
// Insert frames at a specific index
anim.addFrameAt(this.anims.generateFrameNumbers('player', { frames: [5] }), 2);
// Remove a specific frame object
const frame = anim.frames[3];
anim.removeFrame(frame);
// Remove frame at index
anim.removeFrameAt(0);
Aseprite 支持
this.load.aseprite('paladin', 'paladin.png', 'paladin.json');
// In create:
this.anims.createFromAseprite('paladin'); // all tags
this.anims.createFromAseprite('paladin', ['walk']); // specific tags only
sprite.play('walk'); // play by tag name
配置参考
AnimationConfig(与 this.anims.create() 一起使用)
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
key | string | -- | 动画的唯一标识符 |
frames | string 或 AnimationFrame[] | [] | 纹理键字符串(使用所有帧)或帧配置对象数组 |
sortFrames | boolean | true | 使用字符串键时对帧进行数值排序 |
defaultTextureKey | string | null | 未按帧设置时的回退纹理键 |
frameRate | number | 24 | 播放速率,单位为帧每秒(当 duration 为 null 时使用) |
duration | number | null | 动画总时长,单位为毫秒(设置后会推导出 frameRate) |
skipMissedFrames | boolean | true | 落后时跳过帧 |
delay | number | 0 | 播放开始前的延迟(毫秒) |
repeat | number | 0 | 首次播放后重复的次数(-1 = 无限) |
repeatDelay | number | 0 | 每次重复前的延迟(毫秒) |
yoyo | boolean | false | 重复前反向回到起始位置 |
showBeforeDelay | boolean | false | 延迟期间立即显示第一帧 |
showOnStart | boolean | false | 动画开始时设置 visible=true |
hideOnComplete | boolean | false | 动画完成时设置 visible=false |
randomFrame | boolean | false | 从随机帧开始 |
PlayAnimationConfig(与 sprite.play() 一起使用)
所有 AnimationConfig 计时属性均可用,此外还有:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
key | string 或 Animation | -- | 要播放的动画键或动画实例 |
startFrame | number | 0 | 开始播放的帧索引 |
timeScale | number | 1 | 此播放的速度倍数 |
PlayAnimationConfig 中的值会覆盖动画定义,仅对此次特定播放生效。
Duration 与 FrameRate 的优先级
- 如果
duration和frameRate均为 null:默认为 24 fps。 - 如果只设置了
duration:frameRate 计算为totalFrames / (duration / 1000)。 - 如果设置了
frameRate(即使同时设置了 duration):frameRate 优先,duration 推导为(totalFrames / frameRate) * 1000。
事件
Sprite 事件流
animationstart-- 延迟结束后、首次更新前animationupdate-- 每次帧变化时animationrepeat-- 每个重复周期animationcomplete-- 自然结束(有限重复)animationcomplete-{key}-- 同上,附带动画键
手动停止:触发 animationstop 而非 complete。播放中途重新开始:触发 animationrestart。
所有回调:(animation, frame, gameObject, frameKey)。
AnimationManager 事件
在 this.anims 上触发:addanimation、removeanimation、pauseall、resumeall。
API 快速参考
AnimationManager (this.anims)
| 方法 | 说明 |
|---|---|
create(config) | 创建并注册一个全局动画 |
remove(key) | 按 key 移除一个全局动画 |
get(key) | 按 key 获取一个 Animation 实例 |
exists(key) | 检查某个 key 是否已注册 |
generateFrameNumbers(key, config) | 从精灵图集生成帧数组 |
generateFrameNames(key, config) | 从图集生成帧数组 |
play(key, children) | 在一组游戏对象上播放动画 |
staggerPlay(key, children, stagger) | 在多个游戏对象上交错播放 |
pauseAll() / resumeAll() | 全局暂停/恢复所有动画 |
addMix(animA, animB, delay) | 设置两个动画之间的过渡延迟 |
removeMix(animA, animB?) | 移除一组混合配对 |
getMix(animA, animB) | 获取两个动画之间的混合延迟 |
createFromAseprite(key, tags?, target?) | 从 Aseprite JSON 创建动画 |
toJSON() | 将所有动画导出为 JSON 数据 |
fromJSON(data, clear?) | 从 JSON 数据加载动画(传入 true 可先清除现有动画) |
AnimationState (sprite.anims)
| 方法 | 说明 |
|---|---|
play(key, ignoreIfPlaying?) | 播放一个动画 |
playReverse(key, ignoreIfPlaying?) | 反向播放一个动画 |
playAfterDelay(key, delay) | 延迟若干毫秒后播放 |
playAfterRepeat(key, repeatCount?) | 在当前动画重复 N 次后播放 |
chain(key) | 将动画排队在当前动画之后播放 |
stop() | 立即停止 |
stopAfterDelay(delay) | 延迟若干毫秒后停止 |
stopAfterRepeat(repeatCount?) | 再重复 N 次后停止 |
stopOnFrame(frame) | 到达特定帧时停止 |
pause(atFrame?) | 暂停播放 |
resume(fromFrame?) | 恢复播放 |
restart(includeDelay?, resetRepeats?) | 从头重新开始 |
reverse() | 在播放中途反向 |
getName() | 获取当前动画的 key |
getFrameName() | 获取当前帧的 key |
getProgress() | 获取进度 0-1 |
setProgress(value) | 设置进度 0-1 |
setRepeat(value) | 在播放期间更改重复次数 |
getTotalFrames() | 获取总帧数 |
create(config) | 在此精灵上创建一个本地动画 |
exists(key) | 检查某个本地动画是否存在 |
get(key) | 按 key 获取一个本地动画 |
关键属性:isPlaying、hasStarted、currentAnim、currentFrame、forward、inReverse、timeScale。
注意事项
- 默认情况下动画是全局的。
this.anims.create()会在所有 Scene 中注册。不要在每个 Scene 中重复创建 -- 它会记录一条警告并返回已有的那个。 repeat: -1永远不会触发animationcomplete。 使用stop()来结束无限动画。应改为监听animationstop。frameRate优先于duration。 如果两者都设置了,frameRate 会胜出。若要控制总时长,只设置duration(让 frameRate 保持为 null)。- 每帧的
duration是叠加的。 它是在基础 msPerFrame 之上追加的,而不是替换。 play()会停止当前动画(触发animationstop)。如果已经正在播放,可使用play(key, true)跳过。- 混合延迟仅在使用
play()时有效。playAfterDelay/playAfterRepeat会绕过混合。 - 本地动画会覆盖全局。 精灵本地映射中相同 key 的动画优先。
- 精灵简写方法。
sprite.play()、sprite.playReverse()、sprite.chain()、sprite.stop()是对sprite.anims.*的封装。 - 链式动画在停止后也会触发。 如果不希望如此,在停止前用
sprite.anims.chain()清空队列。 generateFrameNumbers的 end=-1 表示最后一帧。__BASE帧会被自动排除。
源文件映射
| 文件 | 用途 |
|---|---|
src/animations/AnimationManager.js | 全局单例 -- 创建、移除、获取、generateFrame*、mix、staggerPlay |
src/animations/Animation.js | 动画定义 -- 帧、时序、yoyo、重复逻辑 |
src/animations/AnimationState.js | 每个精灵的组件 -- 播放、停止、暂停、链式调用、事件 |
src/animations/AnimationFrame.js | 单帧数据 -- textureKey、textureFrame、duration、progress |
src/animations/events/index.js | 所有动画事件常量 |
src/animations/typedefs/Animation.js | AnimationConfig 类型定义 |
src/animations/typedefs/PlayAnimationConfig.js | PlayAnimationConfig 类型定义 |
src/animations/typedefs/GenerateFrameNumbers.js | generateFrameNumbers 的配置 |
src/animations/typedefs/GenerateFrameNames.js | generateFrameNames 的配置 |