| name | phaser-animation |
| description | This skill should be used when the user asks to "create animation", "animate sprite", "add tweens", "sprite animation not playing", "character animations", "easing", "tween timeline", "idle animation", "walk animation", "fade in", "fade out", or "scale animation". |
| version | 0.5.0 |
Phaser 4 Animations and Tweens
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.
Creating Spritesheet Animations
A spritesheet packs multiple frames into a single image in a regular grid. Define animations in AnimationManager using frame indices.
preload(): void {
this.load.spritesheet('player', 'assets/player.png', {
frameWidth: 48,
frameHeight: 48,
});
}
create(): void {
this.anims.create({
key: 'player-idle',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 8,
repeat: -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,
});
this.anims.create({
key: 'player-attack',
frames: this.anims.generateFrameNumbers('player', { start: 16, end: 23 }),
frameRate: 16,
repeat: 0,
});
}
generateFrameNumbers Options
this.anims.generateFrameNumbers('texture', {
start: 0,
end: 7,
first: 0,
frames: [0, 2, 4],
});
Atlas-Based Animations
Texture atlases store frames with named keys rather than grid positions. Use generateFrameNames for these:
this.load.atlas('hero', 'assets/hero.png', 'assets/hero.json');
this.anims.create({
key: 'hero-run',
frames: this.anims.generateFrameNames('hero', {
prefix: 'run_',
start: 1,
end: 8,
zeroPad: 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:
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 {
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().
Playing Animations
sprite.play('player-walk');
sprite.play('player-walk', 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;
sprite.anims.currentFrame?.index;
Animation Events
Listen for animation lifecycle events on the sprite (not the AnimationManager):
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (anim, frame, gameObject) => {
console.log('animation complete:', 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);
Always remove listeners when the sprite is destroyed to prevent memory leaks:
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
sprite.off(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
Animation Chaining
Play a sequence of animations one after another:
sprite.chain(['player-attack', 'player-idle']);
sprite.play('player-attack');
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.
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():
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();
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;
}
}
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.
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'):
- Add it to the
CharState union type.
- Add a
case for it in setState().
- 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.
Stopping and Pausing Animations
sprite.stop();
sprite.anims.pause();
sprite.anims.resume();
sprite.anims.restart();
Tweens
Tweens interpolate any numeric property on any object over time. They are Phaser's primary tool for UI animations, cutscenes, and visual feedback.
this.tweens.add({
targets: sprite,
x: 400,
y: 300,
alpha: 1,
duration: 800,
ease: 'Quad.Out',
delay: 0,
repeat: 0,
yoyo: false,
hold: 0,
onStart: () => {},
onUpdate: () => {},
onComplete: () => {},
});
Common Tween Patterns
Fade In
sprite.setAlpha(0);
this.tweens.add({ targets: sprite, alpha: 1, duration: 400, ease: 'Linear' });
Fade Out and Destroy
this.tweens.add({
targets: sprite,
alpha: 0,
duration: 300,
ease: 'Linear',
onComplete: () => sprite.destroy(),
});
Scale Pulse (hit feedback, collectible)
this.tweens.add({
targets: sprite,
scaleX: 1.3,
scaleY: 1.3,
duration: 80,
ease: 'Quad.Out',
yoyo: true,
});
Slide In From Edge
sprite.setX(-100);
this.tweens.add({
targets: sprite,
x: 400,
duration: 500,
ease: 'Back.Out',
});
Bounce Landing
sprite.setY(targetY - 100);
this.tweens.add({
targets: sprite,
y: targetY,
duration: 600,
ease: 'Bounce.Out',
});
Tween Easing Functions
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
Tween Timelines
Sequence multiple tweens without nesting onComplete callbacks:
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',
},
{
targets: button,
alpha: 1,
duration: 200,
},
],
});
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
Particle Animations (Brief)
For burst effects (explosions, pickups, impacts), use the built-in particle system:
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);
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.