Entity-Component Part 1: What? Why? How?

So you have just finished developing the class for the zombie knight of your upcoming game. His animation is smooth, his AI perfect, you are proud. He had a lot in common with the mad cleric you developed two weeks ago, except for the way he fights, so you just copy-pasted the parts that were similar. You are not going to modify those any time soon anyway, are you? The next day, your friend comes over with a great idea for a new kind of magic for the player. Great, let's implement it. So you start with the zombie knight. Then you remember you also have to modify the code for the mad cleric. And for the ogre. And for the... oops!

What if the common behavior for all your ennemies was unified. Write once, use for everything. Wouldn't that be glorious? My friends, welcome to the magical world of Entity-Component.

What's that?

Entity-Component (or Entity-Component-System), also known as ECS, is an architectural patterns that allows great flexibility is the way entities are managed. Components can be added or removed at runtime from entities, hence adding, removing or modifiying the behavior of entities. The behavior itself is described in systems by requesting all the existing entities having components of a given type.

An entity is only an identifier, an abstract object. It holds no behavior at all. An entity contains components. It can only have one of each component.

A component is data. It defines an aspect of an entity, for example its physical properties in the scene (position, velocity, size), it emitting light (color, intensity), or its animation to be displayed.

A system is a piece of code that retrieves all the entities that have certain types of components and does things on them. For instance, by retrieving all the entities with physical properties, you can make them move through the scene in an unified piece of code. Similarily, by retrieving all the entities emitting light and with physical properties, you can light your scene up from those entities.

Why should I care?

Of course, as said earlier, an advantage of using ECS is how reusable most of your code becomes. The power of ECS arises from combining components. Imagine two components, on for light property (color, intensity), and one that defines a temporary object that will be deleted after a given amount of time. You already have a system for displaying lights, and one for deleting temporary objects when needed. By creating an entity with both those components, you can create flash of light without writing any logic, and you have reused components that can be used for other things. Creating new kinds of entities is now a matter of combining components, and sometimes writing systems to process entities with these new aspects.

But ECS can also make your cache happy. Indeed, an Entity-Component system can be written in a data-oriented way. For those who may not know, the data-oriented paradigms aims at improving memory coherence by using contiguous arrays of data instead of countless pointers referencing memory all over the place. This approach takes advantage of the CPU cache to reduce memory fetch times. In an ECS, this is done by allocating the components of a given type in a large continuous array for example. Iterating through it then induces less potential cache misses.

Oh my! It sounds like the Holy Grail of game development!

It is not!

The first problem you may encounter when using an ECS is how to make your systems communicate. Sure, any data can be transmitted using components, but is it wise for a system that handles a certain event, let's say the death of an ennemy in the game, to iterate all over the entities to find the one that just died? And if there had to be some kind of flags in components for every event is your game, your components may quickly become a mess. I have hinted at a possible solution: events. A solid event system (probably based on an observer pattern) would be a solution to handle such cases.

Another problem hinted at previously is the cost of iterating through the entities in every system. In areas such as collision detection, accelerating structures such a spacial partition structures can be used to filter out entities that need to be processed. However, by using clever tricks is the way entities are retrieved in the entities manager, filtering out uninteresting entities can be really efficient.

So, how do I do it?

Okay, here we go, let's use some pseudo-code and a lot of magic. Let's define some components.

struct PhysicalComponent {
    float x;
    float y;
    float dx;
    float dy;
};

struct PointLightComponent {
    float r;
    float g;
    float b;
};

struct LivingComponent {
    float health;
};

struct SpriteComponent {
    string filename;
}

And now let's make some entities

/**
 * Creates a particle that emits light of a random color
 * moving in a random direction.
 */
Entity createFunkyParticle() {
    Entity e = entitiesManager.create();

    PhysicalComponent phys = {0, 0, rand(), rand()};
    PointLightComponent light = {rand(), rand(), rand()};

    e.addComponent<PhysicalComponent>(phys);
    e.addComponent<PointLightComponent>(light);

    return e;
}

/**
 * Create an NPC.
 */
Entity createNPC() {
    Entity e = entitiesManager.create();

    PhysicalComponent phys = {0, 0, rand(), rand()};
    LivingComponent life = {100};
    SpriteComponent sprite = {"npc.png"};

    e.addComponent<PhysicalComponent>(phys);
    e.addComponent<LivingComponent>(life);
    e.addComponent<SpriteComponent>(sprite);

    return e;
}

Okay, let's make the entities move!

list<Entity> entities = entitiesManager.getEntitiesWith<PhysicalComponent>();

for (Entity e : entities) {
    PhysicalComponent phys = e.getComponent<PhysicalComponent>();

    e.x += e.dx;
    e.y += e.dy;
}

You can see that all the entities move with the same code. Yay. And now let's display all the things.

list<Entity> lights = entitiesManager.getEntitiesWith<PhysicalComponent, LightComponent>();

for (Entity e : lights) {
    PhysicalComponent phys = e.getComponent<PhysicalComponent>();
    LightComponent light = e.getComponent<LightComponent>();

    displayLight(light, phys.x, phys.y);
}

list<Entity> sprites = entitiesManager.getEntitiesWith<PhysicalComponent, SpriteComponent>();

for (Entity e : sprites) {
    PhysicalComponent phys = e.getComponent<PhysicalComponent>();
    SpriteComponent sprite = e.getComponent<SpriteComponent>();

    displaySprite(sprite.filename, phys.x, phys.y);
}

And now we have displayed our entities. All the entities with sprites will be displayed with the same code, and so will the entities representing lights.

Cool, what now?

Well, that's it for the moment. This article aimed at explaining the basics of an ECS and how it can help you. The pseudo-code has given you concrete example of simple tasks done in an entity-component fashion. In the next articles, we will be implementing an ECS in C++. If you see any error, or if you have feedback, contact me!

Comments