5
\$\begingroup\$

I'm looking into how to do a game engine using only C and wanted to know how to implement ECS in C.

I didn't find any ECS implementation for C, it only seems to exist for C++.

So I'm wondering... Is it possible to implement such a feature?

And if so, how to implement such a pattern in C?

\$\endgroup\$
5
  • 5
    \$\begingroup\$ Hello and welcome to Game Development! Generally, yes/no questions are not a great fit for our site. They can usually be reworded to something like "how would I do an ECS in c?", but in the current case, that's a bit broad. Have you tried anything and have you gotten stuck with anything? \$\endgroup\$
    – Vaillancourt
    Commented Jun 4, 2019 at 17:47
  • \$\begingroup\$ Thanks a lot for the feedback, I'm not quite accustomed to the Stack Exchange policies yet. I just searched for possible ECS libraries for C. It is quite true that I should have tried out maybe implementing at least something but feel completely clueless on how to do so. I'll rephrase my question as you suggested! \$\endgroup\$
    – Narice
    Commented Jun 4, 2019 at 17:56
  • 2
    \$\begingroup\$ Can you give an example of a specific aspect of an ECS implementation — maybe a C++ example you like and want to emulate — that you're having trouble adapting to C? \$\endgroup\$
    – DMGregory
    Commented Jun 4, 2019 at 19:37
  • \$\begingroup\$ I'll add this: someone once said "make games, not engines". Remember that the ECS pattern is just a tool. If you get a hammer made of gold and diamonds, it's going to be the shiniest hammer ever, but will be worth crap to drive nails. Deciding to go for an engine and an ECS while you don't know in what context it will be used will have a risk of you writing code that serves no purpose. You might want to focus on an end rather than a means. \$\endgroup\$
    – Vaillancourt
    Commented Jun 6, 2019 at 13:05
  • \$\begingroup\$ Just from my own experience, a proper ECS needs a way to get the union of multiple collections in order to cut down on unnecessary processing. If you don't have a good collections library, you're likely to miss out on one area where some of the performance benefits come from. Take a look at my java example here for more on the concept of ECS \$\endgroup\$
    – Stephan
    Commented Jun 6, 2019 at 18:36

1 Answer 1

11
\$\begingroup\$

As a pattern ECS and data oriented programming in general are closer to pure C than C++.

Entity component systems consist of three major elements.

  • Components are simply holding data; they shouldn't hold any logic. A simple struct should be enough to model them in C.
  • Systems is where all your logic lives.
  • Finally, Entities don't hold anything (logic or data). They are conceptually just a way of connecting components together. These can be represented as an ID in the component.

As an example you may have a game where your entities can have two components. A position component and a health component. These can be represented as two structs. Both structs hold an entity_id.

struct Position
{
    int entity_id;
    int x;
    int y;
};

struct Health
{
    int entity_id;
    int current_health;
    int max_health;
};

For every different type of component you need a list or an array of that component. To make things simpler I put everything in a single struct, sort of the root of the ECS. Also, for simplicity I am using just a member array. You may need to consider a better approach like a binary tree where the key of each node is the id of the component's entity.

struct ComponentLists
{
    Health health_components[100];
    Position position_components[100];
    int total_health_components;
    int total_position_components;
};

Based on these data structres you could define your systems as simple stateless functions. In ECS systems are meant to represent transformations that change the state of a component.

// a simplistic way of creating a new entity
void create_entity(ComponentLists* components)
{
    int id = ENTITIES++;// global number of entities 
    components.health_components[id].entity_id = id;
    components.total_health_components++;
    components.position_components[id].entity_id = id;
    components.total_position_components++;
    //initialise any other variable here
}

void update_health_system(Health* h, ComponentLists* components)
{
    if(h->current_health <= 0) 
    {
        kill_entity(components, h->entity_id);// this will remove all components that refer to that entity_id
    }
}

void update_position_system(Position* p, ComponentLists* components)
{
    if(p->y <= 0)
    { 
        // for simplicity's sake we assume that the id is the index.
        // In a real world example this won't be enough.
        // You may want to use a different data structure instead like a hash map or a binary tree.
        Health* h = components.health_components[p->entity_id];
        h->current_health--;
    }
}

Putting everything together, this is how a main loop could look like.

void main_loop()
{
    ComponentLists components;

    create_entity(components);
    create_entity(components);

    while(1)
    {
        /*Now you can update each system one by one*/
        for(int i = 0; i < components.total_position_components; ++i)
        {
            update_position_system(&components.position_components[i],components);
        }

        for(int i = 0; i < components.total_health_components; ++i)
        {
            update_health_system(&components.health_components[i],components);
        }
    }
}

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thanks a lot for your response! It is extremely complete and helped me understand how it was done. \$\endgroup\$
    – Narice
    Commented Jun 6, 2019 at 11:39
  • 3
    \$\begingroup\$ While most of your answer hits the mark, you lose one of the most powerful aspects of this architecture with the way you structured your loop. The main loop should iterate over systems, not entities. Each system iterates only over the entities that share the cross section of components that are relevant for that particular system. This prevents systems from wasting cycles on entities that have no relevance to the system. Take a look at my answer here for my example in java \$\endgroup\$
    – Stephan
    Commented Jun 6, 2019 at 18:26
  • 4
    \$\begingroup\$ @Stephan Good point! In my effort to simplify this I did miss that completely. In a proper system you wouldn't have all entities have all possible components (ENTITIES in the two for loops should be number_of_components instead.) \$\endgroup\$
    – Exaila
    Commented Jun 6, 2019 at 22:15

Not the answer you're looking for? Browse other questions tagged .