---
name: dev-phaser-sprite-management
description: Sprites, sprite sheets, texture atlases, and object pooling for Phaser
---

# Phaser Sprite Management

> "Efficient sprite handling – sheets, atlases, and object pools."

## Before/After: Manual Arrays vs Phaser Object Pooling

### ❌ Before: Manual Array Management

```typescript
// Manual sprite management without Phaser
interface Bullet {
  x: number;
  y: number;
  vx: number;
  vy: number;
  active: boolean;
}

const bullets: Bullet[] = [];
let lastShotTime = 0;

function shoot(x: number, y: number, dx: number, dy: number) {
  // Create new object every shot
  const bullet: Bullet = {
    x, y,
    vx: dx * 500,
    vy: dy * 500,
    active: true
  };
  bullets.push(bullet);
}

function updateBullets(dt: number) {
  for (let i = bullets.length - 1; i >= 0; i--) {
    const b = bullets[i];
    b.x += b.vx * dt;
    b.y += b.vy * dt;

    // Manual cleanup triggers GC pressure
    if (b.x < 0 || b.x > 800 || b.y < 0 || b.y > 600 || !b.active) {
      bullets.splice(i, 1); // Expensive operation
    }
  }
}

// Problems:
// - New objects every shot (GC pressure)
// - Array splice is expensive
// - No pre-allocation
// - Manual lifecycle management
```

### ✅ After: Phaser Object Pooling

```typescript
// Phaser handles pooling automatically
export class GameScene extends Phaser.Scene {
  private bulletPool!: Phaser.GameObjects.Group;

  create() {
    // Pre-allocate bullet pool
    this.bulletPool = this.add.group({
      defaultKey: 'bullet',
      maxSize: 50, // Never exceeds 50 objects
      createCallback: (bullet: Phaser.GameObjects.Image) => {
        bullet.setActive(false).setVisible(false);
      }
    });

    // Pre-fill pool for performance
    for (let i = 0; i < 20; i++) {
      this.bulletPool.get(0, 0);
    }
  }

  fireBullet(x: number, y: number, vx: number, vy: number) {
    const bullet = this.bulletPool.get(x, y) as Phaser.GameObjects.Image;

    if (bullet) {
      bullet.setActive(true).setVisible(true);

      // Animate and return to pool when done
      this.tweens.add({
        targets: bullet,
        x: x + vx * 2,
        y: y + vy * 2,
        duration: 500,
        onComplete: () => {
          this.bulletPool.killAndHide(bullet);
        }
      });
    }
  }
}

// Benefits:
// - Objects reused (no GC)
// - killAndHide is fast
// - Pre-allocated pool
// - Automatic lifecycle
```

## When to Use This Skill

Use when:

- Creating and managing game sprites
- Working with sprite sheets and texture atlases
- Implementing object pooling for performance
- Optimizing sprite rendering
- Managing sprite animations

## Quick Start

```typescript
// Load sprite sheet in preload()
this.load.spritesheet("player", "assets/player.png", {
  frameWidth: 32,
  frameHeight: 32,
  startFrame: 0,
  endFrame: 15,
});

// Create sprite in create()
const player = this.add.sprite(400, 300, "player", 0);
player.play("idle");
```

## Decision Framework

| Need                  | Use                            |
| --------------------- | ------------------------------ |
| Single image          | `load.image()` + `add.image()` |
| Animation frames      | `load.spritesheet()`           |
| Multiple sprites      | `load.atlas()`                 |
| Many reusable objects | Object pool                    |
| Physics body          | `physics.add.sprite()`         |

## Progressive Guide

### Level 1: Basic Sprite Loading

```typescript
export class MainScene extends Phaser.Scene {
  preload() {
    // Single image
    this.load.image("player", "assets/player.png");

    // Sprite sheet (horizontal strip)
    this.load.spritesheet("coin", "assets/coin.png", {
      frameWidth: 16,
      frameHeight: 16,
      endFrame: 7,
    });

    // Multi-row sprite sheet
    this.load.spritesheet("player", "assets/player.png", {
      frameWidth: 32,
      frameHeight: 48,
      startFrame: 0,
      endFrame: 47,
    });
  }

  create() {
    // Create sprite
    const player = this.add.sprite(400, 300, "player");

    // Set frame manually
    player.setFrame(5);

    // Flip sprite
    player.setFlipX(true);
    player.setFlipY(false);
  }
}
```

### Level 2: Texture Atlas Loading

```typescript
preload() {
  // Load atlas with JSON (from TexturePacker)
  this.load.atlas('game', 'assets/atlas.png', 'assets/atlas.json');

  // Load atlas with XML (from Shoebox)
  this.load.atlasXML('ui', 'assets/ui.png', 'assets/ui.xml');

  // Load atlas with hash array (Unity-style)
  this.load.atlas('items', 'assets/items.png', 'assets/items.json');
}

create() {
  // Create sprite from atlas
  const sword = this.add.image(400, 300, 'game', 'sword.png');
  const shield = this.add.image(400, 350, 'game', 'shield.png');
}
```

### Level 3: Sprite Animations

```typescript
create() {
  // Create animations from sprite sheet
  this.anims.create({
    key: 'idle',
    frames: this.anims.generateFrameNumbers('player', {
      start: 0,
      end: 3
    }),
    frameRate: 10,
    repeat: -1 // Infinite loop
  });

  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
  });

  // Play animation on sprite
  const player = this.add.sprite(400, 300, 'player');
  player.play('idle');

  // Change animation
  player.play('walk', true); // true = ignore if playing
}
```

### Level 4: Object Pooling

```typescript
export class MainScene extends Phaser.Scene {
  private bulletPool!: Phaser.GameObjects.Group;
  private readonly MAX_BULLETS = 50;

  create() {
    // Pre-create bullet pool
    this.bulletPool = this.add.group({
      defaultKey: "bullet",
      maxSize: this.MAX_BULLETS,
      createCallback: (bullet: Phaser.GameObjects.Image) => {
        bullet.setActive(false).setVisible(false);
      },
    });

    // Pre-fill pool
    for (let i = 0; i < 20; i++) {
      this.bulletPool.get(0, 0);
    }

    this.input.on("pointerdown", this.fireBullet, this);
  }

  fireBullet() {
    const bullet = this.bulletPool.get(
      this.player.x,
      this.player.y,
    ) as Phaser.GameObjects.Image;

    if (bullet) {
      bullet.setActive(true).setVisible(true);

      // Animate bullet
      this.tweens.add({
        targets: bullet,
        x: bullet.x + 500,
        duration: 500,
        onComplete: () => {
          // Return to pool
          bullet.setActive(false).setVisible(false);
          this.bulletPool.killAndHide(bullet);
        },
      });
    }
  }
}
```

### Level 5: Advanced Sprite Management

```typescript
export class MainScene extends Phaser.Scene {
  private spriteManager!: SpriteManager;

  create() {
    this.spriteManager = new SpriteManager(this);

    // Register sprite types
    this.spriteManager.registerType("enemy", {
      poolSize: 30,
      onCreate: (sprite) => this.setupEnemy(sprite),
      onActivate: (sprite, data) => this.spawnEnemy(sprite, data),
      onDeactivate: (sprite) => this.cleanupEnemy(sprite),
    });
  }

  update() {
    // Spawn enemy
    this.spriteManager.spawn("enemy", { x: 400, y: 100, type: "flying" });
  }
}

// Sprite Manager class
class SpriteManager {
  private pools = new Map<string, Phaser.GameObjects.Group>();

  constructor(private scene: Phaser.Scene) {}

  registerType(
    type: string,
    config: {
      poolSize: number;
      onCreate: Function;
      onActivate: Function;
      onDeactivate: Function;
    },
  ) {
    this.pools.set(
      type,
      this.scene.add.group({
        defaultKey: type,
        maxSize: config.poolSize,
        createCallback: config.onCreate,
        removeCallback: config.onDeactivate,
      }),
    );
  }

  spawn(type: string, data: any) {
    const pool = this.pools.get(type);
    if (!pool) return null;

    const sprite = pool.get(data.x, data.y);
    if (sprite) {
      sprite.setActive(true).setVisible(true);
      pool.config.onActivate(sprite, data);
    }
    return sprite;
  }

  despawn(type: string, sprite: Phaser.GameObjects.GameObject) {
    const pool = this.pools.get(type);
    if (pool) {
      pool.killAndHide(sprite);
    }
  }
}
```

## Anti-Patterns

❌ **DON'T:**

- Load individual images for sprite frames - use sprite sheets
- Create/destroy sprites every frame - use object pooling
- Use large atlases without grouping - organize by scene/use
- Forget to `killAndHide()` pooled objects before reuse
- Mix pixel densities in sprite sheets
- Use `add.sprite()` when physics needed - use `physics.add.sprite()`

✅ **DO:**

- Use TexturePacker or similar for atlas generation
- Pre-allocate pools during scene creation
- Group atlas textures by logical usage
- Set active/visible false when returning to pool
- Use consistent frame sizes in sprite sheets
- Cache frequently used sprites

## Code Patterns

### Object Pool with Custom Class

```typescript
class Bullet extends Phaser.GameObjects.Image {
  declare body: Phaser.Physics.Arcade.Body;

  constructor(scene: Phaser.Scene, x: number, y: number) {
    super(scene, x, y, 'bullet');
    scene.add.existing(this);
    scene.physics.add.existing(this);
  }

  fire(x: number, y: number, velocity: Phaser.Math.Vector2) {
    this.setPosition(x, y);
    this.body.setVelocity(velocity.x, velocity.y);
    this.setActive(true);
    this.setVisible(true);
  }

  reset() {
    this.body.setVelocity(0, 0);
    this.setActive(false);
    this.setVisible(false);
    this.setPosition(-1000, -1000); // Off-screen
  }
}

// In scene
create() {
  this.bullets = this.add.group({
    classType: Bullet,
    maxSize: 50,
    runChildUpdate: true
  });
}
```

### Sprite Sheet with Multi-Row Animation

```typescript
// 4x4 sprite sheet (16 frames)
// Row 1: Idle (0-3)
// Row 2: Walk (4-7)
// Row 3: Attack (8-11)
// Row 4: Die (12-15)

this.anims.create({
  key: "idle",
  frames: this.anims.generateFrameNumbers("player", {
    start: 0,
    end: 3,
  }),
  frameRate: 8,
  repeat: -1,
  yoyo: false,
});

this.anims.create({
  key: "walk",
  frames: this.anims.generateFrameNumbers("player", {
    start: 4,
    end: 7,
  }),
  frameRate: 12,
  repeat: -1,
});
```

## Checklist

- [ ] Using sprite sheets or atlases (not individual images)
- [ ] Object pools pre-allocated
- [ ] Sprites properly returned to pool
- [ ] Animations defined before use
- [ ] Frame sizes consistent
- [ ] Texture memory optimized
- [ ] Off-screen sprites culled

## Reference

- [Phaser Loader](https://photonstorm.github.io/phaser3-docs/Phaser.Loader.LoaderPlugin.html) — Loading assets
- [Phaser Animations](https://photonstorm.github.io/phaser3-docs/Phaser.Animations.AnimationManager.html) — Animation system
- [Phaser Groups](https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Group.html) — Object grouping


---
Source attribution: feliperyba
Original project: https://github.com/feliperyba/ralph-orchestra/blob/7a74951367fe9bec093753f6eaa4d0eeda9428e5/.claude/skills/dev-phaser-sprite-management/SKILL.md
License: MIT
License information: https://opensource.org/license/mit
Original upstream instructions; downloaded from Dave AI.

## Upstream license notice

MIT License

Copyright (c) 2026 feliperyba

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



# Dave AI resource metadata

Creator / publisher: feliperyba
Original project: https://github.com/feliperyba/ralph-orchestra/blob/7a74951367fe9bec093753f6eaa4d0eeda9428e5/.claude/skills/dev-phaser-sprite-management/SKILL.md
Attribution basis: Recovered original package and original project license at a pinned revision

Fields of work: Development & Engineering
Roles: Frontend Developer, Game Developer
Specialties: Backend, Mobile
Tools: Phaser
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 0b6f661c9a3e1e30a5329f9c8178a46fa2590bdf6e899922752e240334461b0f
Metadata reviewed: 2026-10-27
This metadata supersedes earlier Dave AI catalog attribution. Original instruction and supporting files are preserved.
