phaser-animation
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".
How do I install this agent skill?
npx skills add https://github.com/yakoub-ai/phaser4-gamedev --skill phaser-animationIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides comprehensive documentation and code examples for handling animations and tweens in Phaser 4. No security issues were detected.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
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()
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,
});
}
generateFrameNumbers Options
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)
});
Atlas-Based Animations
Texture atlases store frames with named keys rather than grid positions. Use generateFrameNames for these:
// 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,
});
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:
// 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().
Playing Animations
// 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
Animation Events
Listen for animation lifecycle events on the sprite (not the AnimationManager):
// 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:
sprite.on(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
// In shutdown():
sprite.off(Phaser.Animations.Events.ANIMATION_COMPLETE, this.onAnimComplete, this);
Animation Chaining
Play a sequence of animations one after another:
// 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')
);
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; // 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')
);
}
}
}
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(); // always stop before play on a 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() — the completion event fires after the last frame renders, and update() may run before your handler. 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 ANIMATION_COMPLETE timing 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
CharStateunion type. - Add a
casefor it insetState(). - 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(); // 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
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, // 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
});
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
// Slide in from left
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', // 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
Particle Animations (Brief)
For burst effects (explosions, pickups, impacts), use the built-in particle system:
// 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();
Additional Resources
Reference Files
references/animation-api.md— Complete AnimationManager, AnimationConfig, Animation events, TweenManager, and Timeline API referencereferences/easing-reference.md— All built-in easing functions with descriptions, use cases, and code examplesreferences/state-machine-patterns.md— State-machine discipline for characters:cinematicModeflag, canonical state list, transition table, thestop()/play()ordering rule, andANIMATION_COMPLETEtiming. Read when building any character with more than idle+walk, or when forced animations play for one frame and revert.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/yakoub-ai/phaser4-gamedev/phaser-animation">View phaser-animation on skillZs</a>