Overview

Thanksgiving is this week. Time to make pineapple glazed ham, pecan pie, sweet potatoes, and cranberry sauce. In the meantime, I'm working on developing a video game and I want to talk about Entity Component Systems (ECS). ECS is based on composition over inheritance, and storing data in Structure of Arrays over Array of Structures. Composition is easier to implement coherently, and a Structure of Arrays architecture keeps relevant data together so that it's faster to load into CPU cache.

I'm going to explain ECS in the context of game development. Entities are very simple, they are a simple integer-type identifier that represent any active "thing" that exists in the game. Components are a simple structure of relevant data. A system applies logic to components.

Entities

Your main character is represented by a unique entity. Every bullet fired has a unique entity. Every enemy has an entity. The entity acts like a pointer. The game engine can find all of the relevant components if it knows the entity value.

Components

Components are simple structures, usually literally a 'struct' in C++. A good example is a position component, which identifies a position in 3D space using X, Y, and Z axis coordinates that are floating point variables.

    // Component example
    struct position {
        float x;
        float y;
        float z;
    };

One entity may have many components. A character could have a position, velocity, health, inventory, etc. However, instead of storing all of that in a character class, which could be thousands of bytes in size distributed across the heap, the data is stored in components as a Structure of Arrays.

Array of Structures:
[ character1(\<position\><velocity><health><inventory>), character2(<position><velocity><health><inventory>), character3(<position><velocity><health><inventory>), ...]
Structure of Arrays:
[ <position>, <position>, <position>, ... ]
[ <velocity>, <velocity>, <velocity>, ... ]
[ <health>, <health>, <health>, ... ]
[ <inventory>, <inventory>, <inventory>, ... ]

System

Each system iterates over components and updates them. For example, a gravity system might update a velocity component by applying downward acceleration every frame.

    // System example
    function gravity() {
        for(auto& velocity : velocity_components){
          velocity.z += -9.81 * Time.deltaTime;
        }
    };

The power of this design is that the gravity system can iterate over only the velocity components, rather than iterating over every character, enemy, physics object, whatever that has gravity applied to it. Reading the

[ <velocity>, <velocity>, <velocity>, ... ]
array is much faster than reading the

[ character1(\<position\><velocity><health><inventory>), character2(<position><velocity><health><inventory>), character3(<position><velocity><health><inventory>), ...]

array because data is loaded from memory in pages. If we fit more data into a single page, then we can process the entire array in fewer page loads.

Conclusion

Next time I'll talk more about how ECS enables composition to make development easier and faster.

Back to Home