This guide covers loading AutoSprite PNG spritesheets into Phaser 3 and driving them with the Animation Manager and Arcade Physics.
Need to generate the asset first? Start with the Phaser sprite sheet generator workflow, then use this guide for loader config and animation code.
Quick Start
Drop the PNG into your project
Copy the exported PNG into your game's public/assets/ (or wherever your loader is pointed).
Load in preload()
Phaser's load.spritesheet slices a uniform grid. Use the frameWidth / frameHeight values from AutoSprite's JSON atlas.
preload() {
this.load.spritesheet('player_run', 'assets/player_run.png', {
frameWidth: 256,
frameHeight: 256,
});
}
Create the animation
In create(), register an animation from a frame range:
create() {
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNumbers('player_run', {
start: 0,
end: 11,
}),
frameRate: 12,
repeat: -1, // -1 = loop forever
});
}
Play it on a Sprite
const player = this.physics.add.sprite(100, 300, 'player_run');
player.anims.play('run');
Loading One Animation Per Sheet
AutoSprite exports one PNG per animation by default. Register each one with its own key:
preload() {
const size = { frameWidth: 256, frameHeight: 256 };
this.load.spritesheet('idle', 'assets/idle.png', size);
this.load.spritesheet('run', 'assets/run.png', size);
this.load.spritesheet('jump', 'assets/jump.png', size);
}
create() {
const anims = [
{ key: 'idle', end: 5, fps: 8, loop: true },
{ key: 'run', end: 11, fps: 14, loop: true },
{ key: 'jump', end: 7, fps: 12, loop: false },
];
anims.forEach(({ key, end, fps, loop }) => {
this.anims.create({
key,
frames: this.anims.generateFrameNumbers(key, { start: 0, end }),
frameRate: fps,
repeat: loop ? -1 : 0,
});
});
}
Grab frames, frameWidth, and frameHeight directly from AutoSprite's JSON atlas — no need to count frames by eye.
Using a Combined Atlas
If you merge AutoSprite's outputs into a single packed atlas (with TexturePacker, Free Texture Packer, etc.), switch to load.atlas:
preload() {
this.load.atlas('player', 'assets/player.png', 'assets/player.json');
}
create() {
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNames('player', {
prefix: 'run_',
start: 0,
end: 11,
zeroPad: 2, // run_00, run_01, ...
}),
frameRate: 12,
repeat: -1,
});
}
Atlas loading is worth it once you have many animations — one texture, one HTTP request, and Phaser's texture cache stays small.
Arcade Physics Example
Full Arcade Physics player with facing-direction flip:
create() {
this.player = this.physics.add.sprite(100, 300, 'idle');
this.player.setCollideWorldBounds(true);
this.cursors = this.input.keyboard.createCursorKeys();
}
update() {
const { left, right, up } = this.cursors;
const onGround = this.player.body.blocked.down;
if (left.isDown) {
this.player.setVelocityX(-160);
this.player.setFlipX(true);
if (onGround) this.player.anims.play('run', true);
} else if (right.isDown) {
this.player.setVelocityX(160);
this.player.setFlipX(false);
if (onGround) this.player.anims.play('run', true);
} else {
this.player.setVelocityX(0);
if (onGround) this.player.anims.play('idle', true);
}
if (up.isDown && onGround) {
this.player.setVelocityY(-400);
this.player.anims.play('jump', true);
}
}
Handling Animation Events
React to an animation finishing (e.g. chain attack → idle):
this.player.on('animationcomplete-attack', () => {
this.player.anims.play('idle');
});
Generic listener (fires for any animation):
this.player.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (anim) => {
console.log('finished', anim.key);
});
Pixel Art Tips
In your Phaser game config, set pixelArt: true (or antialias: false) to keep pixel edges crisp. This switches the texture filter to NEAREST.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
pixelArt: true,
physics: { default: 'arcade' },
scene: { preload, create, update },
};
Common Issues
Only the first frame plays: You called play('run') but never created the animation with that key. anims.create must run in create() before play.
Sprite looks blurry: Enable pixelArt: true in the game config.
Frames are off by one: AutoSprite's JSON frames counts the total; Phaser's end is inclusive and zero-indexed, so end = frames - 1.
Wrong frame dimensions: The frameWidth / frameHeight must exactly match the AutoSprite JSON. If your sheet is 4096×256 with 16 frames, frame size is 256×256 — not 4096/16 without checking the PNG height.
Learn More
Need Help?
Join DiscordWe have a #phaser channel for framework-specific questions.
