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);
}
}
}