Every game framework eventually has to answer the same question: how do you represent game objects? The obvious answer — inheritance — falls apart as soon as your game gets interesting.
Player extends Character extends MovableEntity extends Entity
That works until you need a FlyingPlayer. Or a GhostEnemy that phases through walls and can fly. Inheritance chains get brittle fast, and refactoring them is miserable.
Craters uses the Entity-Component-System (ECS) pattern instead.
In Craters, an entity isn’t a class with methods. It’s a unique identifier that components get attached to. No behaviour of its own — just a handle.
const world = new ECS.World();
const player = world.createEntity();
const enemy = world.createEntity();
Neither player nor enemy does anything yet. Behaviour comes from components.
Components hold state for a single concern. You attach them to entities:
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(); }
}
player.addComponent(new Position(100, 200));
player.addComponent(new Velocity(0, 0));
enemy.addComponent(new Position(400, 300));
// enemy has no Velocity — it stays still
player has position and velocity. enemy has only position. No base class, no shared hierarchy — just components on an entity.
Systems run each game tick and process entities that match a specific component combination:
class PhysicsSystem 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;
});
}
}
The physics system doesn’t care what an entity is. It just queries for [Position, Velocity] and processes whatever matches. The enemy gets skipped automatically because it has no Velocity.
| Inheritance | ECS | |
|---|---|---|
| Adding a behaviour | Add a subclass | Add a component |
| Sharing logic | Extract to base class | Write a shared system |
| Composing mixed types | Multiple inheritance hacks | Just add both components |
With inheritance you’d need FlyingEnemy extends Enemy. With ECS:
enemy.addComponent(new Velocity(0, -1));
// PhysicsSystem now picks it up automatically
One line. No subclass needed. The PhysicsSystem query now matches the enemy, and it starts moving on the next tick.
HTML5 games tend to have a lot of object types sharing pieces of behaviour — enemies that also pick up items, platforms that also move, bullets that also spawn particles. ECS handles this without a mess because:
Source: github.com/john-swana/craters