Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ECS Basics

This article gives a conceptual introduction to ECS.

It does not show how Bloop's ECS API works.

Overview

ECS is an acronym standing for Entity Component System. It is a game programming paradigm offering an alternative to other paradigms such as Object-Oriented Programming (OOP).

With OOP, a class encapsulates both the data and functionality of a game object. A class stores properties such as color, velocity, or health, and also contains functions (typically some sort of update() function) to give the game object a behavior.

With ECS, there is no tight coupling of data and functionality. Rather, data and functionality is separated into components and systems, respectively.

Components

Components can be thought of as simple structs. Components may store as many or as few fields as you would like.

Some examples of common components:

struct Health {
    hitpoints: f32,
};

struct Transform {
    position: Vec2,
    rotation: f32,
    scale: Vec2,
};

struct Velocity(Vec2);

Entities

An entity represents an object within the game world. Unlike OOP, an entity by itself contains no data. It is simply a unique identifier for a game object.

The magic happens when you associate an entity with a set of components. An entity may be assigned any number of components. For example, a player entity may be given a Transform and a Health component, a static mesh entity a Transform and a Bounds component, and a projectile a Transform and a Velocity component.

The data associated with an entity is wholly defined by its set of components.

Spawning entities with a hypothetical ECS API could look like:

let entity = spawn(Transform, Velocity);

Systems

We now know how to create entities and describe them with sets of components. The last step is to give entities some behavior. This is where systems come in.

When coming from an OOP background, systems are the least familiar part of ECS. With OOP, you would typically write an update() function for each game object, which describes the behavior of that game object:

struct Projectile {
    position: Vec2;
    velocity: Vec2;

}

impl Projectile {
    fn update(&mut self) {
        self.position += self.velocity;
    }
}

However, with ECS, functionality is separate from data, so you cannot write such a function. Instead, systems are free functions which operate on specific sets of components.

// Pseudocode, simplified to communicate concepts.

fn apply_velocity(query: Query<Transform, Velocity>) {
    for (transform, velocity) in query {
        *transform += velocity;
    }
}

When this function is registered as a system with an ECS engine, it will operate over every entity in the world which has a Transform component and a Velocity component.