Sprites and Animations
Load images, create sprite sheets, and animate game characters in Phaser.
What you'll accomplish
Create animated sprites and understand texture atlases.
Prerequisites
- Working Phaser project
- Understanding of scenes and game loop
What Are Sprites?
A sprite is any 2D image displayed in your game:
- Player character
- Enemies
- Items (coins, power-ups)
- Bullets
- UI elements
Phaser makes it easy to load, display, and animate sprites.
Loading Images
Load images in preload(), use them in create():
typescriptpreload(): void { // Load a single image this.load.image('player', 'assets/player.png'); this.load.image('enemy', 'assets/enemy.png'); this.load.image('background', 'assets/background.jpg'); } create(): void { // Add background (0, 0 is top-left) this.add.image(400, 300, 'background'); // Add player at center this.add.image(400, 300, 'player'); // Add multiple enemies this.add.image(100, 100, 'enemy'); this.add.image(700, 100, 'enemy'); }
Image Properties
Control how images appear:
typescriptconst player = this.add.image(400, 300, 'player'); // Position player.x = 100; player.y = 200; player.setPosition(100, 200); // Same thing // Origin (pivot point, default is 0.5, 0.5 = center) player.setOrigin(0, 0); // Top-left player.setOrigin(0.5, 1); // Bottom-center // Scale player.setScale(2); // Double size player.setScale(0.5, 1); // Half width, normal height // Rotation (in radians) player.rotation = Math.PI / 4; // 45 degrees player.angle = 45; // Same, but in degrees // Flip player.flipX = true; // Mirror horizontally player.flipY = true; // Mirror vertically // Visibility player.visible = false; // Hide player.alpha = 0.5; // 50% transparent // Depth (z-order) player.setDepth(10); // Higher = drawn on top
Sprite Sheets
A sprite sheet is a single image containing multiple frames:
┌────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ ← Walk cycle
├────┼────┼────┼────┤
│ 4 │ 5 │ 6 │ 7 │ ← Jump animation
└────┴────┴────┴────┘
Loading Sprite Sheets
typescriptpreload(): void { // Load sprite sheet with frame dimensions this.load.spritesheet('player', 'assets/player-sheet.png', { frameWidth: 32, frameHeight: 48, }); } create(): void { // Create sprite (shows first frame by default) const player = this.add.sprite(400, 300, 'player'); // Show a specific frame player.setFrame(2); }
Creating Animations
Define animations from sprite sheet frames:
typescriptcreate(): void { // Create animations this.anims.create({ key: 'walk', frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }), frameRate: 10, repeat: -1, // -1 = loop forever }); this.anims.create({ key: 'jump', frames: this.anims.generateFrameNumbers('player', { start: 4, end: 7 }), frameRate: 10, repeat: 0, // Play once }); this.anims.create({ key: 'idle', frames: [{ key: 'player', frame: 0 }], frameRate: 1, }); // Create player sprite const player = this.add.sprite(400, 300, 'player'); // Play animation player.play('walk'); }
Animation Options
typescriptthis.anims.create({ key: 'explosion', frames: this.anims.generateFrameNumbers('explosion', { start: 0, end: 15 }), frameRate: 24, repeat: 0, // -1 = loop, 0 = once, n = n times yoyo: true, // Play forward then backward hideOnComplete: true, // Hide sprite when done });
Playing Animations
typescriptconst player = this.add.sprite(400, 300, 'player'); // Play animation player.play('walk'); // Play animation, ignore if already playing player.play('walk', true); // Stop animation player.stop(); // Pause/resume player.anims.pause(); player.anims.resume(); // Play from specific frame player.play({ key: 'walk', startFrame: 2 }); // Animation events player.on('animationcomplete', (anim: Phaser.Animations.Animation) => { if (anim.key === 'death') { player.destroy(); } });
Texture Atlases
For many sprites, use a texture atlas - a sprite sheet with named frames:
Atlas JSON Format
json{ "frames": { "player_idle_1": { "frame": { "x": 0, "y": 0, "w": 32, "h": 48 } }, "player_idle_2": { "frame": { "x": 32, "y": 0, "w": 32, "h": 48 } }, "player_walk_1": { "frame": { "x": 64, "y": 0, "w": 32, "h": 48 } } } }
Loading Atlases
typescriptpreload(): void { this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json'); } create(): void { // Use frame by name this.add.image(100, 100, 'sprites', 'player_idle_1'); // Create animation from atlas this.anims.create({ key: 'walk', frames: this.anims.generateFrameNames('sprites', { prefix: 'player_walk_', start: 1, end: 4, }), frameRate: 10, repeat: -1, }); }
Tip
Use tools like TexturePacker or Free Texture Packer to create atlases automatically.
Physics Sprites
For sprites that need physics (movement, collision):
typescriptcreate(): void { // Add as physics sprite this.player = this.physics.add.sprite(400, 300, 'player'); // Physics body properties this.player.body.setSize(24, 40); // Collision box size this.player.body.setOffset(4, 8); // Offset from sprite corner // Movement this.player.setVelocity(100, 50); this.player.setVelocityX(100); // Bounce this.player.setBounce(0.2); // Don't go outside screen this.player.setCollideWorldBounds(true); // Gravity this.player.body.setGravityY(300); }
Practical Example: Animated Player
typescriptimport Phaser from 'phaser'; export class GameScene extends Phaser.Scene { private player!: Phaser.Physics.Arcade.Sprite; private cursors!: Phaser.Types.Input.Keyboard.CursorKeys; constructor() { super('GameScene'); } preload(): void { this.load.spritesheet('player', 'assets/player.png', { frameWidth: 32, frameHeight: 48, }); } create(): void { // Create animations this.anims.create({ key: 'idle', frames: this.anims.generateFrameNumbers('player', { start: 0, end: 0 }), frameRate: 1, }); this.anims.create({ key: 'walk', frames: this.anims.generateFrameNumbers('player', { start: 1, end: 4 }), frameRate: 10, repeat: -1, }); this.anims.create({ key: 'jump', frames: this.anims.generateFrameNumbers('player', { start: 5, end: 5 }), frameRate: 1, }); // Create player this.player = this.physics.add.sprite(400, 300, 'player'); this.player.setCollideWorldBounds(true); // Get keyboard input this.cursors = this.input.keyboard!.createCursorKeys(); } update(): void { const speed = 160; const body = this.player.body as Phaser.Physics.Arcade.Body; // Horizontal movement if (this.cursors.left.isDown) { body.setVelocityX(-speed); this.player.flipX = true; this.player.play('walk', true); } else if (this.cursors.right.isDown) { body.setVelocityX(speed); this.player.flipX = false; this.player.play('walk', true); } else { body.setVelocityX(0); this.player.play('idle', true); } // Jump (if on ground) if (this.cursors.up.isDown && body.blocked.down) { body.setVelocityY(-330); } // Jump animation while in air if (!body.blocked.down) { this.player.play('jump', true); } } }
Tweens (Programmatic Animation)
For non-sprite-sheet animations, use tweens:
typescriptcreate(): void { const coin = this.add.image(400, 300, 'coin'); // Simple tween this.tweens.add({ targets: coin, y: 250, duration: 500, yoyo: true, repeat: -1, ease: 'Sine.inOut', }); // Complex tween this.tweens.add({ targets: coin, alpha: { from: 0, to: 1 }, scale: { from: 0.5, to: 1 }, rotation: Math.PI * 2, duration: 1000, ease: 'Bounce.out', onComplete: () => { console.log('Tween finished!'); }, }); }
Common Easing Functions
Linear- Constant speedQuad.in/out/inOut- Accelerate/decelerateBounce.out- Bouncing effectElastic.out- Springy effectSine.inOut- Smooth wave
Common Issues
Frequently Asked Questions
Check:
- File path is correct (relative to
public/folder) - Image loaded in
preload()before used increate() - No console errors about missing files
- Image isn't behind another object (check depth)
- Check
frameWidthandframeHeightmatch your sprite sheet - Verify frame numbers (starts at 0)
- Console log frames to debug:
console.log(this.anims.get('walk').frames)
For pixel art, disable anti-aliasing:
typescriptconst config = { pixelArt: true, // or render: { antialias: false, }, };
Check the repeat value:
-1= loop forever0= play oncen= play n+1 times
Verify Your Understanding
You've completed this lesson if you can:
Next: Add physics for movement, gravity, and collision.