Skip to content

ECS in dacha

Entity-component-system is a way of organising a game that separates what a thing is from what a thing does. Instead of a deep class hierarchy where Player extends Character extends GameObject, you have plain entities that own bags of data, and separate logic units that operate on every entity holding a particular combination of data.

dacha follows that idea, with its own vocabulary and one addition.

An actor owns data-only components. A system queries actors by component filter. A behavior attaches to a single actor. Actorthe entity that owns thingsowns ↓TransformdataSpritedataColliderdataSystemlogic, per scene or worldquery bycomponent filterBehaviorlogic, for one actorattached to
Components hold data and never logic. A system selects the actors it acts on by filtering on components, while a behavior is attached to one actor and runs only for it.

The thing a game is made of is called an actor. It owns components, it can own child actors, and it has an identity and a name.

The word “entity” is taken: Entity is an internal base class that actors, scenes and the world all extend, which is where the hierarchy and event-target behaviour come from. When this documentation says actor, it means the game object.

A component is a bag of values attached to an actor. Transform holds a position, rotation and scale. Sprite holds which image to draw. Collider holds a shape.

None of them contain logic. That is not stylistic preference, it is a structural requirement: the whole game is described as data that a visual editor reads and writes. A component with behaviour in it could not be serialised into that file, and the editor could not offer it in the inspector.

If you find logic creeping into a component, it belongs in a system or a behavior.

A system runs every frame and acts on the actors it cares about. It does not keep a list by hand; it declares which components it needs and receives the matching actors, including ones spawned later:

import { ActorQuery, Transform } from 'dacha';
import { Velocity } from './velocity.component';
const query = new ActorQuery({
scene,
filter: [Transform, Velocity],
});
for (const actor of query.getActors()) {
const transform = actor.getComponent(Transform);
const velocity = actor.getComponent(Velocity);
// …
}

The filter can also be a predicate when a component list is not expressive enough:

const query = new ActorQuery({
scene,
filter: (actor) => actor.getComponent(Transform).world.position.y > 100,
});

Systems come in two kinds depending on whether they survive a scene change. That distinction has its own page.

A textbook ECS has no equivalent of a behavior. A behavior attaches to a single actor and runs for that actor alone.

The reason it exists is that not all logic is naturally a sweep over many actors. A door that opens when approached, a camera that follows a target, a boss with a scripted sequence: writing each as a system that queries exactly one actor is awkward, and writing it as a behavior is not. See behaviors.

Use a system when the logic applies to a class of actors. Use a behavior when it applies to one.

Almost every constraint here follows from one decision: the game is described by data that a visual editor edits. Components have to be serialisable, so they hold no logic. Systems have to be listed and configured in that file, so they are named and take options. Actors have to be arrangeable in a tree, so they form a hierarchy.

The payoff is that the editor is not a separate authoring tool bolted onto a code-first engine. It edits the same thing the engine runs.