Sprites, sprite sheets, texture atlases, and object pooling for Phaser
Installable skillMITBy feliperybaUpdated Oct 27, 2026
INTENDED FOROpenClawInstall in a listed agent.
instructions.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
---
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
Platform guidance
Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.
Resource notes updated 2026-10-27 · Original instructions by feliperyba
Creates comprehensive frontend UI/UX designs including user interfaces, design systems, component libraries, responsive layouts, and accessibility implementations. Produces wireframes, mockups, design specifications, and implementation guidelines. Use when designing user interfaces, creating desi...
Expert in building scalable design systems with Atomic Design, design tokens, and theming infrastructure. Use when creating component libraries, implementing dark mode, or establishing typography and color systems. Covers multi-brand support, Storybook-driven development, and accessibility-first ...
Use when implementing Liquid Glass effects, reviewing UI for Liquid Glass adoption, debugging visual artifacts, optimizing performance, or requesting expert review of Liquid Glass implementation - provides comprehensive design principles, API patterns, and troubleshooting guidance from WWDC 2025....