🎮 Game Dev · Data-Oriented Design
📅 Березень 2026⏱ ≈ 10 хв читання🟡 Середній

Entity Component System

Traditional game architectures use inheritance hierarchies that scale poorly and abuse the CPU cache. ECS flips the design: instead of objects that have behaviour, we have data tables (components) that functions (systems) transform. The result can be 10–100× faster for large worlds.

1. The OOP Problem

Classic game object hierarchies look like: Entity → Actor → Pawn → Character → FPSCharacter. Problems at scale:

OOP approach

  • GameObject with all possible fields
  • Virtual update() methods per type
  • Inheritance for "kinds" of objects
  • Pointer-chased per-object allocation
  • Feature = subclass or flag

ECS approach

  • Entity = just an ID (integer)
  • Components = pure data structs
  • Systems = functions over component sets
  • Dense array storage per component type
  • Feature = add/remove component

2. Core Concepts

Minimal ECS in JavaScript const world = new World(); const eid = createEntity(world); addComponent(world, Position, eid); Position.x[eid] = 0; // array-of-structs or struct-of-arrays Position.y[eid] = 0; // System function moveSystem(world, dt) { const query = defineQuery([Position, Velocity]); for (const eid of query(world)) { Position.x[eid] += Velocity.x[eid] * dt; Position.y[eid] += Velocity.y[eid] * dt; } }

3. Archetype Storage

The dominant storage strategy is archetypes: group entities that have exactly the same set of components into contiguous arrays. An archetype is defined by its component set (e.g., {Position, Velocity, Health} → archetype A).

Entity IDs
entity 1
entity 4
entity 7
Position[]
{1.0, 2.0, 0.0}
{3.5, 0.0, 1.2}
{-1.0, 4.0, 0.0}
Velocity[]
{0.1, 0.0, 0.0}
{0.0, 0.5, 0.0}
{-0.2, 0.1, 0.0}
Health[]
100
80
55

All components for entities in an archetype are stored in dense arrays. System iteration = sequential memory access → excellent cache performance. Adding a component to an entity moves it to a different archetype.

An alternative is sparse sets: per component type, maintain a sparse→dense mapping. Better for very large entity ID spaces or frequent archetype changes. Flecs uses archetypes; bitECS uses sparse sets; both can be cache-friendly.

4. Systems and Queries

A system declares a query: the required, optional, and excluded component types. The ECS engine returns matching entities. Systems can be:

Flecs C++ query example world.system<Position, const Velocity>("Move") .each([](Position& p, const Velocity& v) { p.x += v.x * DT; p.y += v.y * DT; }); // Query with exclusion: world.query_builder<Position>() .without<Frozen>() .build();

5. Cache Efficiency

Modern CPUs are 10–100× faster than RAM access latency. A cache line is 64 bytes. Random pointer chasing kills performance; sequential array access maximises cache line reuse.

Archetype ECS naturally delivers SoA per component, per archetype. A simple physics system iterating 100,000 entities can run in ~1 ms (cache-friendly) vs ~10 ms with pointer-chased OOP objects.

Real benchmark: Unity's ECS DOTS physics with 100,000 dynamic rigidbodies runs at 60 fps; equivalent GameObject-based physics struggles above ~5,000 objects. The difference is almost entirely cache efficiency + SIMD auto-vectorisation of dense float arrays.

6. Relationships and Tags

Modern ECS frameworks extend beyond flat components:

7. Implementations

When to use ECS: ECS shines for large numbers of similar objects (bullets, particles, units, AI agents). For small projects with <1,000 heterogeneous objects, classic OOP or component-on-object patterns are simpler. ECS has higher upfront complexity cost.