✚ Game development

Entity Component System: organising a game's code

What the Entity Component System architecture is, why it is preferred over deep inheritance, and how entities, components and systems work together.

As a game grows, the hardest question is not about graphics or physics, but about how the code is organised. How do you represent an enemy that flies, one that shoots, one that flies and shoots, a crate that can be destroyed and one that can also be pushed? The Entity Component System (ECS) model was created precisely to answer this question flexibly.

The problem with deep inheritance

The intuitive approach, especially for people coming from classic object-oriented programming, is to build a class hierarchy:

Object
 └─ Character
     ├─ Player
     └─ Enemy
         ├─ FlyingEnemy
         └─ ShootingEnemy

Trouble arrives when you need an enemy that flies and shoots. You have to duplicate code, resort to multiple inheritance, or move everything into the base class, which becomes huge and full of behaviour that most subclasses never use. After a few months of development the hierarchy becomes rigid: every new idea from the game designer requires restructuring it.

Composition instead of inheritance

ECS turns the perspective around. Instead of asking «what is» an object, you ask «what does it have». The model rests on three concepts.

Entities

An entity is just an identifier, often an integer. It contains no data and no logic: it is a label to which components are attached.

Components

A component is a small container of pure data, with no behaviour:

  • Position { x, y }
  • Velocity { vx, vy }
  • Sprite { image, frame }
  • Health { points }
  • Flying { altitude }
  • Weapon { rate, damage }

An enemy that flies and shoots is simply an entity with Position, Velocity, Sprite, Health, Flying and Weapon. A destructible crate has Position, Sprite and Health. No hierarchy, no duplication.

Systems

A system contains the logic and operates on every entity that has a certain combination of components. The movement system, for example, processes all entities with Position and Velocity:

for each entity with (Position p, Velocity v):
    p.x += v.vx * dt
    p.y += v.vy * dt

The rendering system considers entities with Position and Sprite; the damage system those with Health. Each system does one thing and does it for every object concerned.

The practical advantages

Flexibility

Adding a behaviour means adding a component. If the game designer wants crates to be pushable, you just give them Velocity and a Pushable component, then write the matching system. Components can also be added or removed during play: a frozen enemy temporarily loses Velocity, and the movement system automatically stops moving it.

Performance

In data-oriented implementations, components of the same type are stored in contiguous arrays. When the movement system iterates over all positions and velocities, the processor reads sequential memory and makes the best use of its cache. With thousands of entities the difference compared with objects scattered in memory and reached through pointers can be significant.

Separation of concerns

Data and logic are cleanly separated. Saving the game, for instance, becomes simpler: you only need to serialise the components, which are pure data. Testing is easier too, because a system can be checked by feeding it a few hand-built entities.

Where ECS is less convenient

It is not a universal solution. Some aspects require care:

  • Relationships between entities: a weapon held by a character, a carriage coupled to a train. These are handled with components holding another entity's identifier, but you must check that the linked entity still exists.
  • System order: the input system must run before movement, movement before collisions. The order has to be defined explicitly.
  • Very specific logic: a final boss with unique behaviour may be more readable as a dedicated script than as a set of generic components.
  • Learning curve: for a small game with a few dozen objects, ECS can feel like needless complication.

Engines adopt similar ideas in different forms. Unity offers the classic GameObject and MonoBehaviour model, based on components that contain logic, plus a separate data-oriented architecture for projects that need it. Godot is built on a tree of nodes, a different form of composition with similar goals. There are also standalone ECS libraries for many languages, useful if you work with a lightweight framework.

A design example

Suppose you need to build a small scrolling shooter. One possible breakdown:

System Required components
Input PlayerControlled, Velocity
Movement Position, Velocity
Shooting Weapon, Position
Collisions Position, Collider
Damage Health, collision events
Rendering Position, Sprite
Cleanup ToDestroy

The cleanup system, run last, removes entities marked during the frame. Destroying entities while other systems are still iterating over them is a classic source of bugs, and it is better to defer the operation to the end of the loop.

In short

  • ECS replaces class hierarchies with composition: an object is defined by what it has.
  • Entities are identifiers, components are pure data, systems are the logic.
  • Adding behaviour means adding components, even during play.
  • Components stored in contiguous arrays improve performance with many entities.
  • System order, relationships and entity destruction must be designed carefully.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also