Claude-skill-registry dev-phaser-animations
Sprite animations, tweens, animation chains, and timeline sequences
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dev-phaser-animations" ~/.claude/skills/majiayu000-claude-skill-registry-dev-phaser-animations && rm -rf "$T"
manifest:
skills/data/dev-phaser-animations/SKILL.mdsource content
Phaser Animations
"Bring your sprites to life with smooth animations and tweens."
Before/After: Manual Animation vs Phaser Animation System
❌ Before: Manual Frame Animation
// Manual sprite animation without Phaser class SpriteAnimator { private currentFrame = 0; private frameTimer = 0; private frameDuration = 83; // ~12 FPS private isPlaying = false; private frames: HTMLImageElement[] = []; constructor(private sprite: HTMLElement) { // Load all frames manually for (let i = 0; i < 8; i++) { const img = document.createElement('img'); img.src = `assets/walk/frame${i}.png`; this.frames.push(img); } } play(animationName: string) { this.isPlaying = true; this.currentFrame = 0; } update(dt: number) { if (!this.isPlaying) return; this.frameTimer += dt; if (this.frameTimer >= this.frameDuration) { this.frameTimer = 0; this.currentFrame = (this.currentFrame + 1) % this.frames.length; this.sprite.style.backgroundImage = `url(${this.frames[this.currentFrame].src})`; } } // Manual tween for position moveTo(targetX: number, targetY: number, duration: number) { const startX = parseFloat(this.sprite.style.left) || 0; const startY = parseFloat(this.sprite.style.top) || 0; const startTime = performance.now(); const animate = (currentTime: number) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Linear interpolation only - no easing options this.sprite.style.left = (startX + (targetX - startX) * progress) + 'px'; this.sprite.style.top = (startY + (targetY - startY) * progress) + 'px'; if (progress < 1) { requestAnimationFrame(animate); } }; requestAnimationFrame(animate); } } // Problems: // - Manual frame timing is error-prone // - No built-in easing functions // - Manual sprite sheet management // - No animation state management // - Chained animations require nested callbacks // - No timeline support
✅ After: Phaser Animation System
// Phaser handles all animation automatically export class GameScene extends Phaser.Scene { create() { const player = this.add.sprite(400, 300, 'player'); // Create sprite sheet animation - ONE config! this.anims.create({ key: 'walk', frames: this.anims.generateFrameNumbers('player', { start: 0, end: 7 }), frameRate: 12, repeat: -1 // Infinite loop }); // Play animation - ONE line! player.play('walk'); // Tween with easing - ONE call! this.tweens.add({ targets: player, x: 600, y: 400, duration: 1000, ease: 'Power2', // Built-in easing! onComplete: () => { console.log('Movement complete!'); } }); // Timeline for sequences - ONE config! this.tweens.timeline({ targets: player, tweens: [ { alpha: 0, duration: 500, ease: 'Linear' }, { x: 200, duration: 500, offset: '-=250' }, // Overlap! { scale: 2, duration: 300, ease: 'Back.easeOut' } ] }); } } // Benefits: // - Automatic frame timing // - 20+ built-in easing functions // - Sprite sheet generation helpers // - Animation state management (isPlaying, currentAnim) // - Timeline support for complex sequences // - Event callbacks (onComplete, animationcomplete)
When to Use This Skill
Use when:
- Creating sprite animations
- Building tween sequences
- Implementing timeline-based animations
- Animating UI elements
- Creating visual effects and transitions
Quick Start
create() { // Create animation from sprite sheet this.anims.create({ key: 'walk', frames: this.anims.generateFrameNumbers('player', { start: 0, end: 7 }), frameRate: 12, repeat: -1 }); // Play animation const player = this.add.sprite(400, 300, 'player'); player.play('walk'); }
Decision Framework
| Need | Use |
|---|---|
| Frame-based animation | + |
| Property animation | |
| Sequenced animations | |
| Single tween | once |
| Delayed action | |
Progressive Guide
Level 1: Sprite Animations
export class GameScene extends Phaser.Scene { create() { // Create animations from sprite sheet this.anims.create({ key: "idle", frames: this.anims.generateFrameNumbers("player", { start: 0, end: 3, }), frameRate: 8, repeat: -1, // Infinite loop yoyo: false, // Don't play backwards }); this.anims.create({ key: "walk", frames: this.anims.generateFrameNumbers("player", { start: 4, end: 11, }), frameRate: 12, repeat: -1, }); this.anims.create({ key: "attack", frames: this.anims.generateFrameNumbers("player", { start: 12, end: 17, }), frameRate: 15, repeat: 0, // Play once }); // Create sprite and play animation const player = this.add.sprite(400, 300, "player"); player.play("idle"); // Change animation player.play("walk", true); // true = ignore if playing } }
Level 2: Tweening Properties
create() { const box = this.add.rectangle(400, 300, 50, 50, 0xff0000); // Basic tween this.tweens.add({ targets: box, x: 600, duration: 1000, ease: 'Power2' }); // Multiple properties this.tweens.add({ targets: box, x: 200, y: 400, alpha: 0.5, angle: 180, scale: 2, duration: 2000, ease: 'Elastic.easeOut', onComplete: () => { console.log('Tween complete!'); } }); // Yoyo tween (ping-pong) this.tweens.add({ targets: box, y: 100, duration: 1000, yoyo: true, repeat: -1 }); }
Level 3: Tween Chains and Callbacks
create() { const sprite = this.add.sprite(400, 300, 'player'); // Chain tweens this.tweens.add({ targets: sprite, y: 200, duration: 500, ease: 'Linear', onComplete: () => { // Second tween this.tweens.add({ targets: sprite, x: 600, duration: 500, ease: 'Linear', onComplete: () => { // Third tween this.tweens.add({ targets: sprite, scale: 2, duration: 300, ease: 'Back.easeOut' }); } }); } }); // Better approach: Timeline const timeline = this.tweens.timeline({ targets: sprite, tweens: [ { y: 200, duration: 500, ease: 'Linear' }, { x: 600, duration: 500, ease: 'Linear' }, { scale: 2, duration: 300, ease: 'Back.easeOut' } ] }); // Timeline with delay this.tweens.timeline({ tweens: [ { targets: sprite, alpha: 0, duration: 500, offset: 0 // Start immediately }, { targets: otherSprite, y: 400, duration: 500, offset: '-=250' // Start 250ms before previous ends } ] }); }
Level 4: Animation State Management
class AnimationController { private sprite: Phaser.GameObjects.Sprite; private currentAnim = ''; constructor(sprite: Phaser.GameObjects.Sprite) { this.sprite = sprite; // Listen for animation completion this.sprite.on('animationcomplete', this.onAnimComplete, this); } play(animKey: string, ignoreIfPlaying = false) { if (ignoreIfPlaying && this.currentAnim === animKey) { return; } // Don't interrupt non-looping animations const current = this.sprite.anims.currentAnim; if (current && !current.repeat && current.isPlaying) { return; } this.sprite.play(animKey); this.currentAnim = animKey; } onAnimComplete(event: any) { if (event.key === 'attack') { this.play('idle'); } else if (event.key === 'death') { this.sprite.setVisible(false); } } isPlaying(animKey: string): boolean { return this.sprite.anims.isPlaying && this.sprite.anims.currentAnim?.key === animKey; } getProgress(): number { return this.sprite.anims.getProgress(); } } // In scene create() { this.player = this.add.sprite(400, 300, 'player'); this.animController = new AnimationController(this.player); this.animController.play('idle'); } update() { if (this.cursors.left.isDown || this.cursors.right.isDown) { this.animController.play('walk'); } else { this.animController.play('idle'); } if (this.attackKey.isDown && !this.animController.isPlaying('attack')) { this.animController.play('attack'); } }
Level 5: Advanced Animation Systems
export class GameScene extends Phaser.Scene { private animManager!: AnimationManager; create() { this.animManager = new AnimationManager(this); // Define animation states this.animManager.registerState("player", "idle", { frames: { start: 0, end: 3 }, frameRate: 8, loop: true, }); this.animManager.registerState("player", "walk", { frames: { start: 4, end: 11 }, frameRate: 12, loop: true, }); this.animManager.registerState("player", "jump", { frames: { start: 12, end: 15 }, frameRate: 10, loop: false, next: "idle", }); this.animManager.registerState("player", "attack", { frames: { start: 16, end: 21 }, frameRate: 15, loop: false, next: "idle", callback: this.onAttackComplete, }); // Set up state transitions this.animManager.addTransition("player", "idle", "walk", () => this.isMoving(), ); this.animManager.addTransition( "player", "walk", "idle", () => !this.isMoving(), ); this.animManager.addTransition("player", "walk", "jump", () => this.justJumped(), ); // Initialize player this.player = this.add.sprite(400, 300, "player"); this.animManager.attach("player", this.player); } update() { this.animController.update("player"); } isMoving() { return this.cursors.left.isDown || this.cursors.right.isDown; } justJumped() { return Phaser.Input.Keyboard.JustDown(this.jumpKey); } onAttackComplete() { console.log("Attack finished"); } } class AnimationManager { private states = new Map<string, Map<string, AnimationState>>(); private sprites = new Map<string, Phaser.GameObjects.Sprite>(); private transitions: Array<{ sprite: string; from: string; to: string; condition: () => boolean; }> = []; constructor(private scene: Phaser.Scene) {} registerState(sprite: string, key: string, config: any) { if (!this.states.has(sprite)) { this.states.set(sprite, new Map()); } const stateConfig = { ...config, key: `${sprite}_${key}`, }; // Create Phaser animation this.scene.anims.create({ key: stateConfig.key, frames: this.scene.anims.generateFrameNumbers(sprite, config.frames), frameRate: config.frameRate, repeat: config.loop ? -1 : 0, yoyo: config.yoyo || false, }); this.states.get(sprite)!.set(key, stateConfig); } attach(spriteKey: string, sprite: Phaser.GameObjects.Sprite) { this.sprites.set(spriteKey, sprite); sprite.play(`${spriteKey}_idle`); } addTransition( sprite: string, from: string, to: string, condition: () => boolean, ) { this.transitions.push({ sprite, from, to, condition }); } update(spriteKey: string) { const sprite = this.sprites.get(spriteKey); if (!sprite) return; const currentAnim = sprite.anims.currentAnim?.key.replace( `${spriteKey}_`, "", ); const states = this.states.get(spriteKey); // Check transitions for (const transition of this.transitions) { if ( transition.sprite === spriteKey && transition.from === currentAnim && transition.condition() ) { this.play(spriteKey, transition.to); break; } } } play(spriteKey: string, stateKey: string) { const sprite = this.sprites.get(spriteKey); const state = this.states.get(spriteKey)?.get(stateKey); if (sprite && state) { sprite.play(state.key); if (state.callback) { sprite.once("animationcomplete-" + state.key, state.callback); } } } }
Anti-Patterns
❌ DON'T:
- Create animations in update() - do it once in create()
- Override playing animation without checking
- Use tweens for simple value changes
- Forget to clean up event listeners
- Use
for simple sequences - use timelinetweens.chain() - Ignore animation repeat/yoyo properties
✅ DO:
- Create animations once in create()
- Check current animation before changing
- Use tweens for property animation
- Clean up listeners on shutdown
- Use timeline for sequences
- Configure repeat and yoyo appropriately
Code Patterns
Animation Playback Control
// Check if animation is playing if (sprite.anims.isPlaying) { console.log("Playing:", sprite.anims.currentAnim?.key); } // Get animation progress (0-1) const progress = sprite.anims.getProgress(); // Pause/resume animation sprite.anims.pause(); sprite.anims.resume(); // Stop animation sprite.anims.stop(); // Restart animation sprite.anims.restart();
Tween Easing Functions
// Linear this.tweens.add({ targets: obj, x: 100, ease: "Linear" }); // Power easing this.tweens.add({ targets: obj, x: 100, ease: "Power0" }); // Linear this.tweens.add({ targets: obj, x: 100, ease: "Power1" }); // Smooth this.tweens.add({ targets: obj, x: 100, ease: "Power2" }); // Accelerating this.tweens.add({ targets: obj, x: 100, ease: "Power3" }); // More accelerating this.tweens.add({ targets: obj, x: 100, ease: "Power4" }); // Most accelerating // Special easing this.tweens.add({ targets: obj, x: 100, ease: "Elastic.easeOut" }); this.tweens.add({ targets: obj, x: 100, ease: "Bounce.easeOut" }); this.tweens.add({ targets: obj, x: 100, ease: "Back.easeOut" }); this.tweens.add({ targets: obj, x: 100, ease: "Cubic.easeOut" });
Tween Controls
const tween = this.tweens.add({ targets: sprite, alpha: 0, duration: 1000, }); // Pause tween tween.pause(); // Resume tween tween.resume(); // Stop tween tween.stop(); // Complete tween immediately tween.complete();
Common Easing Functions
| Ease | Description |
|---|---|
| Constant speed |
| Same as Linear |
| Gentle ease |
| Medium ease |
| Strong ease |
| Strongest ease |
| Bouncy effect |
| Bounce at end |
| Overshoot slightly |
| Smooth sine curve |
Checklist
- Animations created in create()
- Frame rates appropriate for smoothness
- Repeat/yoyo configured correctly
- Transitions handled gracefully
- Tweens use proper easing
- Event listeners cleaned up
- Animation state managed properly
Reference
- Animation Manager — Animation API
- Tween Manager — Tween API
- Easing Functions — Easing reference