Phaser
35 minutes
linux
macos
windows

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():

typescript
preload(): 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:

typescript
const 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

typescript
preload(): 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:

typescript
create(): 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

typescript
this.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

typescript
const 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

typescript
preload(): 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):

typescript
create(): 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

typescript
import 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:

typescript
create(): 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 speed
  • Quad.in/out/inOut - Accelerate/decelerate
  • Bounce.out - Bouncing effect
  • Elastic.out - Springy effect
  • Sine.inOut - Smooth wave

Common Issues

Frequently Asked Questions

Check:

  1. File path is correct (relative to public/ folder)
  2. Image loaded in preload() before used in create()
  3. No console errors about missing files
  4. Image isn't behind another object (check depth)
  • Check frameWidth and frameHeight match 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:

typescript
const config = {
  pixelArt: true,
  // or
  render: {
    antialias: false,
  },
};

Check the repeat value:

  • -1 = loop forever
  • 0 = play once
  • n = play n+1 times

Verify Your Understanding

You've completed this lesson if you can:

0/5

Next: Add physics for movement, gravity, and collision.