ECS Archetypes System

I’m creating a game engine from scratch. Last time I wrote about the entity component system and how that works. This time I want to explain how the entities get created.


Doing this makes uses of something I’m calling an archetype. I don’t think this is a specific software design pattern, but it’s quite similar to the prototype pattern from the Game Programming Patterns book.

The problem this pattern tries to solve is that when a new entity is created, it needs certain default values setting for all the components. And instead of manually programming those into the new entity, you have a main template of default values to stamp into the new instance. Originally I had a simple initialisation function that did this

void entity_good_create(int entity_id)
{
    velocities[entity_id].active = true;
    positions[entity_id].active = true;
    behaviours[entity_id].active = true;
    colliders[entity_id].active = true;
    debugsprites[entity_id].active = true;
    behaviours[entity_id].behaviour_func = behaviour_good;
    int x, y = 50;
    switch (RAND_BETWEEN(0,2 )) {
        case 0: x = 128; break;
        case 1: x = 160; break;
        case 2: x = 192; break;
    }
    positions[entity_id].x = x;
    positions[entity_id].y = y;
    velocities[entity_id].xv = 0;
    velocities[entity_id].yv = 1;
    velocities[entity_id].speed = 1;
    colliders[entity_id].type = COLLIDER_RECT;
    colliders[entity_id].shape.rect.half_width = 2;
    colliders[entity_id].shape.rect.half_height = 2;
    colliders[entity_id].body_type = BODY_DYNAMIC;
    debugsprites[entity_id].colour = 14;    // Cyan
}

However doing this has a few disadvantages. The main one being it’s difficult to change the default values. This was an issue I didn’t even think of at the time, it only became evident when trying to write a test game.

Another issue is… well look at the code… there’s loads of tedious boilerplate code to type out. Miss off one of those something[entity_id].active = true lines and that component isn’t part of the entity, leading to very hard to locate bugs.

The game

This game has a falling item and the aim is to catch it to earn a point. Miss the item the game ends. Clearly it’s not that exciting, so I decided after catching the item 3 times the game should speed up.

The speed of the item is set when it is created, with a function that gets called by the ECS. I have no easy way to pass in a parameter to control the speed, and I really didn’t want to have a global variable as that’d defeat the whole point of the entity component system.

This is where the archetype system comes in

UML Diagram

The general idea is to create a table of default values for each entity type’s components. Then when a new instance of that entity is created, the default values are copied out the table into the components for that new instance.

Although making this work in C is a bit more convoluted.

There needs to be a place to store a default instance of each component structure, so it can be copied later.

typedef struct {
    void *component_defaults[COMP_COUNT];
} Archetype_t;

Which goes into an array, one for each type of entity the system knows about.

Archetype_t archetypes[ENTITY_TYPE_MAX];

Each entity then has a function to define its default values

void entity_good_prototype()
{
    Velocity_t v = { .active = true, .xv = 0, .yv = 0, .speed = 1 };
    SET_ARCHETYPE(ENTITY_TYPE_GOOD, COMP_VELOCITY, v);
    Position_t p = { .active = true, .x = 0, .y = 50};
    SET_ARCHETYPE(ENTITY_TYPE_GOOD, COMP_POSITION, p);
    Behaviour_t b = { .active = true, .behaviour_func = behaviour_good, .state = BEHAVIOUR_SPAWNING, .prev_state = -1, .timer = 0 };
    SET_ARCHETYPE(ENTITY_TYPE_GOOD, COMP_BEHAVIOUR, b);
    Collider_t c = { .active = true, .type = COLLIDER_RECT,
        .shape.rect.half_width = 2,
        .shape.rect.half_height = 2,
        .body_type = BODY_DYNAMIC };
    SET_ARCHETYPE(ENTITY_TYPE_GOOD, COMP_COLLIDER, c);
    DebugSprite_t d = { .active = true,.colour = 13 };
    SET_ARCHETYPE(ENTITY_TYPE_GOOD, COMP_DEBUGSPRITE, d);
}

There’s a somewhat terrifying C macro to make accessing the relevant part of the archetypes table easier

#define SET_ARCHETYPE(et,ct,val) archetypes[et].component_defaults[ct] = \
memcpy(malloc(sizeof(val)), &val, sizeof(val))

But we don’t talk about that…

The end result is very neat and tidy though. And this is what a good API should do - hide the complicated stuff from the end user.

And then creating an instance involves this generic code, further decoupling what an entity is made from with the way it is made.

Instead of me needing to manually type out what components an entity needs, this function uses the archetypes table to do it for me.

void em_compose_components(EntityType_e type, int entity_id)
{
    for (int i = 0; i < COMP_COUNT; i++) {
        if (archetypes[type].component_defaults[i]) {
            void *comp = compreg_add_comp(entity_id, i);
            memcpy(comp, archetypes[type].component_defaults[i],
                   component_info[i].size);
        }
    }
}

All this so that when the player catches three falling things, the speed increases.

// If we caught a good piece
    if (player != -1 && good != -1) {
        global_data.score++;
        if (global_data.score %3 == 0) {
            game_speed++;
            if (game_speed > 8) game_speed = 8;
            Velocity_t *vel = GET_ARCHETYPE(ENTITY_TYPE_GOOD,COMP_VELOCITY,Velocity_t);
            vel->speed = game_speed;
            vel = GET_ARCHETYPE(ENTITY_TYPE_BAD,COMP_VELOCITY,Velocity_t);
            vel->speed = game_speed;
        }
        spawn_dropper();
        em_destroy(good);
    }

Yeah there’s a global data struct, but it might be temporary. Currently it’s just so different screens of the game can know the score.

It’s a lot of background effort, but a lot of that was just making the concept work in C, rather than the concept itself being complex.

Subscribe

Support

Recent Content

  • ECS Archetypes System November 29, 2025

    ...
  • Agon Light Joystick Interface November 9, 2025

    ...
  • Trying to Be More Organised November 5, 2025

    ...

Archives