Stop being the product.
Become the owner.
or
sign uplog in
Building a 2D Game Engine from scratch in pure ES6 Vanilla JS. Here is how I handle SpriteSheets & AnimatedSprites!

Hi everyone!

I've been working on **BeeEngine 2D**, a lightweight HTML5 game engine built with **pure Vanilla JS (ES6)** and direct Canvas API—no external libraries, no build tools, and zero heavy frameworks.

A lot of modern tools hide sheet slicing behind JSON files, but I wanted a bare-metal, highly efficient approach that gives 100/100 on Lighthouse Performance and instant loading times.

I separated the asset slicing logic from the animation timing logic into two distinct classes:

1. `BeeSpriteSheet`: Handles frame dimensions, columns, rows, and coordinate calculations.
2. `BeeAnimatedSprite`: Handles frame timing, state, loops, and `flipX` transformations.

Here is my `BeeAnimatedSprite` class implementation:

export class BeeAnimatedSprite {
    constructor(spriteSheet, config = {}) {
        this.sheet = spriteSheet;
        this.animations = config.animations || {};
        this.currentAnimName = config.animation || Object.keys(this.animations)[0];


        this.currentFrameIndex = 0;
        this.timer = 0;
        this.flipX = false;
    }


    play(name) {
        if (this.currentAnimName !== name && this.animations[name]) {
            this.currentAnimName = name;
            this.currentFrameIndex = 0;
            this.timer = 0;
        }
    }


    update(dt) {
        const anim = this.animations[this.currentAnimName];
        if (!anim || !anim.frames || anim.frames.length === 0) return;


        const fps = anim.fps || 8;
        const frameDuration = 1 / fps;


        this.timer += dt;


        if (this.timer >= frameDuration) {
            this.timer -= frameDuration;


            if (anim.loop) {
                this.currentFrameIndex = (this.currentFrameIndex + 1) % anim.frames.length;
            } else {
                this.currentFrameIndex = Math.min(this.currentFrameIndex + 1, anim.frames.length - 1);
            }
        }
    }


    draw(ctx, x, y, options = {}) {
        const anim = this.animations[this.currentAnimName];
        if (!anim) return;


        const frameToDraw = anim.frames[this.currentFrameIndex];
        const width = options.width || this.sheet.frameWidth;
        const height = options.height || this.sheet.frameHeight;


        ctx.save ();


        if (this.flipX) {
            // 2. Sposta l'origine al bordo destro dell'immagine e specchia l'asse X
            ctx.translate(x + width, y);
            ctx.scale(-1, 1);


           
            this.sheet.drawFrame(ctx, frameToDraw, 0, 0, width, height);
        } else {
            // Disegno normale senza specchio
            this.sheet.drawFrame(ctx, frameToDraw, x, y, width, height);
        }


        ctx.restore();
    }
}

And here is how
I implemented it inside main.js:


const megaSheetImg = gioco.getAsset('spritesheet_totale');


       
        const frameW = 128;
        const frameH = 128;


        const apeSheet = new BeeSpriteSheet(megaSheetImg, frameW, frameH, {
            col: 3,
            row: 3,
            framesPerRow: 1,
            frameCount: 2
        });


        this.giocatore.sprite = new BeeAnimatedSprite(apeSheet, {
            animation: "fly",
            animations: {
                fly: { frames: [0, 1], fps: 4, loop: true }
            }
        });

Let me know what you think, bearing in mind that this particular combination is super quick to put together.
#js #dev #programming #technology
source
earnings
0 mlx total
$0  total
engagement
2 views
0 reactions
0 comments