How Craters Uses the Entity-Component-System Pattern

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.

The three pieces

Entities — just IDs

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 — data

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 — where logic lives

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.

Comparing approaches

  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

A quick example: making the enemy move

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.

Why this works well for browser games

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:

  • Composable — mix components freely
  • Testable — systems are just functions of component state
  • Performant — queries can be cached, irrelevant entities are skipped

Source: github.com/john-swana/craters

comments powered by Disqus