Skip to content

ECS Fundamentals

Entity-Component-System (ECS) is an architectural pattern that separates an object’s identity (Entity), its state (Components), and its behaviour (Systems).

A unique ID with no state or logic of its own: just a handle for one “thing” in Rift - a player, an NPC, a tree, a projectile. An entity does nothing until Components are attached to it.

A pure data container describing a single trait or attribute (Position, Equipment, Poisoned, Speaking, Skulled). Components hold values, never logic, and are attached to entities to give them substance.

The logic that acts on the data. A system queries every entity carrying a specific set of components and operates on them. All game logic lives in systems.

Example: MovementSystem finds everything with both Position and Moving and advances it.

ECS is not a silver bullet, but it’s unique approach comes with nice benefits for building games.

With ECS you declare intent and let systems carry it out. You never write how an effect is applied - you state the outcome you want.

To hurt an entity you don’t reach in and write: hitpoints = hitpoints - 5

Instead you attach a Damage(amount=5) component, and the system that owns damage resolves the rest. Logic stops being something you call and becomes something you request - which means the same request behaves identically no matter who makes it.

An entity has no fixed type - it is simply the sum of the components attached to it. A player is whatever carries Position, Equipment, and Hitpoints; a campfire is whatever carries Position and Flammable.

Because any component can attach to any entity, you build new kinds of things by combining small, independent traits rather than by extending a class. There are no base classes to subclass and no hierarchy to fight when a thing needs to be two categories at once. In Rift you describe what an entity has, never what it is - composition over inheritance, always.

Components hold data, systems hold logic, and neither knows about the other. A system operates on every entity matching its query without caring where those entities came from or what else they carry.

Adding a feature means adding a component and a system - it touches nothing that already exists. PoisonSystem doesn’t need to know MovementSystem exists; they share entities, not code. Behaviour slots in instead of threading through, so the codebase grows by accretion rather than entanglement.

Rift executes its systems in the same fixed order every game cycle, against a consistent snapshot of the world. The result is that each tick is a pure function of the one before it.

World(N+1) = f(World(N))

There is no hidden, order-dependent state deciding who “went first,” which makes runtime behaviour easy to predict, easy to test, and reproducible: the same inputs always yield the same world.

Because every entity can accept every component, a behaviour is built a single time and serves the entire game. Write the rule that drains Hitpoints for anything holding Poison, and you’ve made everything poisonable at once - players, npcs, objects in the world, and future entities that don’t exist yet.

Make a thing burn, freeze, or bleed by granting it the matching component; the system already knows what to do. Effort spent on one behaviour pays out across every entity that will ever carry it.

Because an entity is just a set of components, entity types become data rather than code.

A new monster is a table of components and values (Hitpoints(120), Aggressive, Poisonous, Skulled) that a game designer can author without writing a single system. Hundreds of variants emerge from recombining a handful of traits, and content scales without growing the engine.