Skip to content
Back to skills

Dev Phaser Sprite Management

ASecurity

Sprites, sprite sheets, texture atlases, and object pooling for Phaser

  • 666 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added February 8, 2026
documentationtypescriptgitperformance

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill dev-phaser-sprite-management --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dev Phaser Sprite Management?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dev Phaser Sprite Management
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-dev-phaser-sprite-management/badge)](https://www.skillsdirectory.com/skills/majiayu000-dev-phaser-sprite-management)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.