I’ve been building browser games on and off since I was a teenager — nothing serious, mostly messing around. At some point I kept running into the same problem: the raw Canvas API is fine, but the moment you want any structure in your game code, you end up writing the same boilerplate every time. The big frameworks felt like too much. I just wanted something lightweight I could actually understand.
So I built Craters.
Craters is a modular game framework for HTML5, written in TypeScript. It started as a JavaScript project and I rewrote it from scratch in TypeScript — more on why in a later post.
The core of it is an Entity-Component-System (ECS) architecture. If you haven’t worked with ECS before, the short version is:
| Concept | What it does |
|---|---|
| Entity | A unique ID — represents a “thing” in your game |
| Component | Data (and sometimes behaviour) attached to an entity |
| System | Logic that runs each tick on entities with specific components |
ECS works well for games because it keeps things composable. You don’t end up with a Player extends Character extends MovableObject chain that breaks when you need something weird.
npm install craters
Here’s a minimal setup using the current API:
import { EntityComponentSystem as ECS, RenderLoop } from 'craters';
class Position extends ECS.Component {
constructor(public x: number, public y: number) { super(); }
}
class Velocity extends ECS.Component {
constructor(public dx: number, public dy: number) { super(); }
}
class MovementSystem extends ECS.System {
execute(delta: number) {
const query = this.world.createQuery([Position, Velocity]);
query.entities.forEach(entity => {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x += vel.dx * delta;
pos.y += vel.dy * delta;
});
}
}
const world = new ECS.World();
world.registerSystem(new MovementSystem());
const player = world.createEntity();
player.addComponent(new Position(0, 0));
player.addComponent(new Velocity(1, 0));
new RenderLoop(delta => world.execute(delta));
That’s a working game loop with an entity that moves.
Out of the box, Craters ships with:
World, Entity, System, Query)The short answer: TypeScript found real bugs in my code that I never would have caught otherwise. The longer answer is in the next post.
Source: github.com/john-swana/craters