AR
AgentRuss
AgentRuss Guide

Understanding Entity Component Systems (ECS): A Beginner-Friendly Guide

Written by
AgentRuss
Published

What Is an Entity Component System?

An Entity Component System, usually shortened to ECS, is an architectural pattern commonly used in games, simulations, and other systems that need to manage large numbers of objects with different behaviours.

At first, ECS can look more complicated than traditional object-oriented programming.

You may be used to creating classes such as:

JavaScript
1class Player {
2 constructor() {
3 this.health = 100
4 this.position = { x: 0, y: 0, z: 0 }
5 this.speed = 5
6 }
7
8 move() {
9 // movement logic
10 }
11
12 takeDamage(amount) {
13 // damage logic
14 }
15}

Everything related to the player lives inside the Player class.

ECS takes a very different approach.

Instead of building increasingly large objects containing both data and behaviour, ECS separates the game into three main concepts:

  1. Entities
  2. Components
  3. Systems

The easiest way to understand ECS is to look at each of those separately.


1. Entities Are Just Identities

An entity represents something that exists in the game.

Examples might include:

  • Player
  • Enemy
  • Sword
  • Health potion
  • Tree
  • Door
  • Projectile
  • Chest
  • NPC

The important part is that the entity itself usually contains very little information.

In a simple ECS, an entity might literally just be an ID:

JavaScript
1const player = 1
2const enemy = 2
3const sword = 3

Or your ECS may generate them automatically:

JavaScript
1const player = world.createEntity()

The entity doesn't necessarily know that it's a player.

It becomes a player because of the components attached to it.


2. Components Contain Data

Components describe what an entity has.

For example, we might create a Transform component:

JavaScript
1class Transform {
2 constructor(x = 0, y = 0, z = 0) {
3 this.x = x
4 this.y = y
5 this.z = z
6 }
7}

A Health component:

JavaScript
1class Health {
2 constructor(current = 100, max = 100) {
3 this.current = current
4 this.max = max
5 }
6}

And a Velocity component:

JavaScript
1class Velocity {
2 constructor(x = 0, y = 0, z = 0) {
3 this.x = x
4 this.y = y
5 this.z = z
6 }
7}

Notice something important.

These components contain data, but they don't contain game logic.

Health doesn't have:

JavaScript
1health.takeDamage()

Velocity doesn't have:

JavaScript
1velocity.move()

Instead, systems perform those operations.

This separation is one of the main ideas behind ECS.


3. Systems Contain Logic

Systems operate on entities that have the components they require.

Imagine that an entity has both:

  • Transform
  • Velocity

A movement system can find those entities and update their positions.

Conceptually:

JavaScript
1class MovementSystem {
2 update(world, deltaTime) {
3 const entities = world.query(Transform, Velocity)
4
5 for (const entity of entities) {
6 const transform = world.getComponent(entity, Transform)
7 const velocity = world.getComponent(entity, Velocity)
8
9 transform.x += velocity.x * deltaTime
10 transform.y += velocity.y * deltaTime
11 transform.z += velocity.z * deltaTime
12 }
13 }
14}

The MovementSystem doesn't care whether the entity is:

  • a player
  • an enemy
  • a projectile
  • a moving platform

If it has the required components, the system can process it.

This is where ECS starts becoming powerful.


Composition Instead of Inheritance

Traditional game architecture often uses inheritance.

You might start with:

JavaScript
1GameObject
2
3Character
4
5Enemy
6
7FlyingEnemy

Eventually you can end up with complicated inheritance trees.

What happens when you need an enemy that:

  • flies
  • takes damage
  • can be poisoned
  • drops loot
  • has an inventory
  • can interact with objects

With ECS, you build behaviour by combining components.

For example:

JavaScript
1Enemy Entity
2
3Transform
4Health
5Velocity
6EnemyAI
7LootTable
8StatusEffects

Want it to fly?

Add:

JavaScript
1Flying

Want it to have an inventory?

Add:

JavaScript
1Inventory

Want another enemy to be stationary?

Simply don't give it a Velocity component.

Instead of asking:

What class does this object inherit from?

ECS encourages you to ask:

What capabilities does this entity have?

A Simple Player Entity

A player might be constructed from components like this:

JavaScript
1const player = world.createEntity()
2
3world.addComponent(player, new Transform(0, 1, 0))
4world.addComponent(player, new Velocity())
5world.addComponent(player, new Health(100, 100))
6world.addComponent(player, new PlayerControlled())

The PlayerControlled component can simply act as a marker.

For example:

JavaScript
1class PlayerControlled {}

Now a PlayerControllerSystem can query:

JavaScript
1PlayerControlled
2Transform
3Velocity

and know that those entities should respond to player input.


Marker Components

Some components don't need to store any values at all.

These are often called marker components or tag components.

For example:

JavaScript
1class Dead {}

When an entity dies, you could add:

JavaScript
1world.addComponent(entity, new Dead())

A system can then query entities with the Dead component.

Similarly:

JavaScript
1class PlayerControlled {}
2class StaticBody {}
3class DynamicBody {}
4class Interactable {}

The presence of the component itself communicates information about the entity.


Queries Are an Important Part of ECS

Systems need an efficient way to discover which entities they should process.

That's where queries come in.

For example:

JavaScript
1world.query(Transform, Velocity)

means:

Give me every entity that currently has both Transform and Velocity.

A damage-related system might query:

JavaScript
1world.query(Health)

A player movement system might query:

JavaScript
1world.query(PlayerControlled, Transform, Velocity)

A collision system might look for:

JavaScript
1world.query(Transform, Collider)

As your game grows, queries become one of the most important parts of your ECS architecture.


Why Keep Components Data-Only?

Technically, you could put methods inside components.

But keeping components focused on data provides some major advantages.

Systems Become Easier to Understand

All movement logic lives inside movement-related systems.

All damage logic lives inside damage-related systems.

You don't need to search through dozens of entity classes to discover where something happens.

Components Become Reusable

A Health component doesn't care whether it belongs to:

  • a player
  • a boss
  • a destructible barrel
  • a training dummy

The same data structure can be reused everywhere.

Behaviour Becomes Composable

Adding or removing components can change what an entity is capable of doing.

That makes ECS especially useful for games where objects can gain and lose abilities dynamically.


Example: Damage in an ECS

Imagine an attack deals 25 damage.

Instead of calling:

JavaScript
1enemy.takeDamage(25)

you might create a damage request or event:

JavaScript
1eventBus.emit('damage', {
2 target: enemy,
3 amount: 25,
4})

A DamageSystem can process the request:

JavaScript
1class DamageSystem {
2 update(world) {
3 const events = world.events.consume('damage')
4
5 for (const event of events) {
6 const health = world.getComponent(event.target, Health)
7
8 if (!health) continue
9
10 health.current -= event.amount
11 }
12 }
13}

Other systems can react afterward.

For example:

JavaScript
1DamageSystem
2
3DeathSystem
4
5LootSystem
6
7RespawnSystem

This makes each system responsible for one clear part of the gameplay pipeline.


ECS Doesn't Mean Everything Must Be a System

One common mistake when learning ECS is trying to force absolutely everything into entities, components, and systems.

Your game will still have normal supporting architecture.

For example:

JavaScript
1Renderer
2Asset Manager
3Event Bus
4Audio Manager
5Input Manager
6Networking
7Save System
8Scene Loader

Some of these may integrate closely with your ECS, but they don't necessarily need to become ECS components themselves.

ECS is an architectural tool, not a rule saying every line of your game must exist inside the ECS.


Is ECS Faster?

ECS is often discussed in terms of performance.

Large data-oriented ECS implementations can be extremely efficient because similar component data can be stored together in memory and processed in batches.

However, performance is not the only reason to use ECS.

For many JavaScript and indie game projects, the architectural benefits can be just as valuable:

  • clear separation of responsibilities
  • reusable gameplay logic
  • easier composition
  • fewer complicated inheritance trees
  • easier addition of new gameplay features
  • systems that can operate across many entity types

A simple ECS doesn't need to be an extremely optimized data-oriented engine to be useful.


When ECS Starts to Make Sense

ECS becomes especially useful when you start building systems that interact with each other.

For example:

JavaScript
1Input
2
3Player Controller
4
5Movement
6
7Collision
8
9Combat
10
11Damage
12
13Death
14
15Loot

Your player, enemies, NPCs, projectiles, and other game objects can participate in those systems based on which components they have.

As features increase, composition makes it easier to extend the game without creating enormous classes.


A Useful Mental Model

If you're struggling to understand whether something should be an Entity, Component, or System, try asking these three questions.

Entity

What thing am I talking about?

Examples:

JavaScript
1Player
2Enemy
3Sword
4Chest
5Projectile

Component

What data or capability does that thing have?

Examples:

JavaScript
1Transform
2Health
3Inventory
4Velocity
5Collider

System

What logic operates on entities with that data?

Examples:

JavaScript
1MovementSystem
2DamageSystem
3InventorySystem
4CollisionSystem

A simple way to remember it is:

Entities are IDs. Components are data. Systems are logic.

That sentence captures the foundation of ECS surprisingly well.


Final Thoughts

Entity Component Systems can feel unusual if you're coming from traditional object-oriented programming.

Instead of creating increasingly specialised classes, ECS encourages you to build game objects through composition.

An entity is an identity.

Components describe the entity's data and capabilities.

Systems contain the logic that operates on those components.

Once that separation becomes familiar, ECS makes it much easier to reason about increasingly complex gameplay systems.

You don't need to build the world's most sophisticated ECS implementation to benefit from the architecture either.

Start simple.

Create entities.

Attach data-only components.

Build small systems with clear responsibilities.

Then let the architecture grow alongside your game.