iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Sprites & Sheets

A sprite is a 2D image rendered into a scene, often animated by swapping frames or by playing back a sub-region of a sprite sheet. Engines wrap this with pivot, scale, tint, flipping, and blending controls.

Sheet animation in Phaser / Pixi

EXAMPLE
// 1) Phaser 3 — sprite from an image
this.load.image('player', 'assets/player.png');

// in create()
const p = this.add.sprite(200, 300, 'player');
p.setOrigin(0.5, 1);              // pivot at bottom-center (feet on ground)
p.setScale(2);                    // pixel-art style: integer scale
p.setFlipX(true);                  // face left
p.setTint(0xff8888);               // damage flash

// 2) Sprite sheet — uniform grid of frames
this.load.spritesheet('hero', 'assets/hero.png', {
    frameWidth:  32,
    frameHeight: 48,
    margin:      0,
    spacing:     0,
});

// Animation = sequence of frame indices
this.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNumbers('hero', { start: 0, end: 7 }),
    frameRate: 12,
    repeat: -1,                    // loop forever
});
this.anims.create({
    key: 'jump',
    frames: this.anims.generateFrameNumbers('hero', { frames: [8, 9, 10] }),
    frameRate: 8,
    repeat: 0,
});

const hero = this.add.sprite(100, 200, 'hero', 0);
hero.play('walk');

// 3) Atlas (TexturePacker) — non-uniform, named frames
this.load.atlas('fx', 'assets/fx.png', 'assets/fx.json');
this.anims.create({
    key: 'explode',
    frames: this.anims.generateFrameNames('fx', {
        prefix: 'explosion_',
        start: 0, end: 11,
        zeroPad: 2,
        suffix: '.png',
    }),
    frameRate: 24,
    hideOnComplete: true,
});
this.add.sprite(x, y, 'fx').play('explode');

// 4) Physics body sized to art (don't use the whole frame)
this.physics.add.existing(hero);
hero.body.setSize(20, 40);          // hitbox narrower than the 32-wide art
hero.body.setOffset(6, 8);

// 5) Pixi.js — sprites from a sheet
import { Application, Assets, AnimatedSprite, Spritesheet } from 'pixi.js';

const app = new Application();
await app.init({ background: '#222', resizeTo: window });
document.body.appendChild(app.canvas);

const sheetData = {
    frames: {
        'walk_0.png': { frame: { x: 0,   y: 0, w: 32, h: 48 } },
        'walk_1.png': { frame: { x: 32,  y: 0, w: 32, h: 48 } },
        'walk_2.png': { frame: { x: 64,  y: 0, w: 32, h: 48 } },
        'walk_3.png': { frame: { x: 96,  y: 0, w: 32, h: 48 } },
    },
    meta: { scale: 1 },
};

const texture = await Assets.load('assets/hero.png');
const sheet   = new Spritesheet(texture, sheetData);
await sheet.parse();

const hero2 = new AnimatedSprite(Object.values(sheet.textures));
hero2.animationSpeed = 0.2;          // frames per tick
hero2.anchor.set(0.5, 1);
hero2.play();
app.stage.addChild(hero2);

// 6) Performance tips
//   • Pack art into atlases — one texture, many draws → fewer batch breaks
//   • Use the same blend mode in a group to keep the batcher hot
//   • Disable culling explicitly for static UI; let the engine cull for the world
//   • Pool sprites (especially for bullets, particles) instead of new/destroy
//
class BulletPool {
    constructor(scene, key, size = 100) {
        this.scene = scene;
        this.group = scene.add.group({
            classType: Phaser.GameObjects.Sprite,
            defaultKey: key,
            maxSize: size,
        });
    }
    fire(x, y, vx, vy) {
        const b = this.group.get(x, y);
        if (!b) return;
        b.setActive(true).setVisible(true);
        this.scene.physics.add.existing(b);
        b.body.setVelocity(vx, vy);
    }
    despawn(b) { b.setActive(false).setVisible(false); b.body.stop(); }
}

// 7) Pixel-perfect rendering
this.game.config.pixelArt = true;           // turn off bilinear filtering
this.cameras.main.setZoom(3);               // integer zoom
hero.setScale(1);                            // never fractional for pixel art

// 8) Tinting + flashing on hit
function flash(sprite) {
    sprite.setTint(0xffffff);
    sprite.scene.time.delayedCall(80, () => sprite.clearTint());
}

// 9) Layering with depth
background.setDepth(-10);
platform.setDepth(0);
hero.setDepth(10);
ui.setDepth(100);

// 10) Common bugs
//   • Origin at (0.5, 0.5) but using setPosition for feet on ground → floats
//   • Body size matches art size → hitbox too generous
//   • One sprite per file → tons of HTTP requests + batch breaks
//   • Animation key collisions between scenes → 'animation already exists'
//   • Forgetting hideOnComplete on a one-shot effect → corpse left in scene
//   • Fractional scale on pixel art → blurry mess

Why it matters

Pack sprites into an atlas, give each animation a dedicated key, and pool fast-spawned sprites like bullets and particles. The art looks the same either way, but draw calls and GC pressure are the difference between 60fps and a stuttery 22.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Sprite sheets pack frames in one image.
// Update animation by indexing into the sheet each tick.
function draw(ctx, sheet, frame, x, y) {
    ctx.drawImage(sheet, frame * 32, 0, 32, 32, x, y, 32, 32);
}
Try it Yourself »

Discussion

Loading…