After 2 Years of attempting and failing to create an Entity component system I think I finally managed to successfully build one. It's not that I have been working on it everyday trying to figure it out. It's because a little less than 2 Years ago I started to write in C more. One of the first things I made was a Pong clone and Since then I've researched and learned a whole lot about Data Oriented Design. This includes ECS architecture. Since I do software development for I don't always have the desire to do programming in my free time. So it's been sort of an off and on process (mostly off though).
Im sorry in advanced if my writing is not very clear
ECS is an architecture design that uses a Data Oriented Approach for building out a program. It essentially utilizes common characteristics in a group of objects to break them out and manage those common characteristics in a single place instead of having a function for each object that does the same thing To read more about data oriented Design you can visit this link which will give plenty of information on it (I honestly haven't read much of it and I'm probably not doing everything right). ECS is composed of Entities, Components and Systems.
Entities are simply unique identifiers used to link up data. I've created a Type Definition Called Entity that contains one field called id.
I initiate an Entity using a function with an int parameter that then stores it in a global struct called world
void create_entity(int entityId)
{
world->entities[entityId] = entityId;
}
read further to find out what world is
Components are simple models that will contain data.
typedef struct {
float x[MAX_ENTITIES];
float y[MAX_ENTITIES];
} Position;
typedef struct {
float x[MAX_ENTITIES];
float y[MAX_ENTITIES];
} Velocity;
typedef struct {
char *path[MAX_ENTITIES];
} Texture;
I learned that the most efficient way to store data in ECS is to follow a Structure of Arrays (SoA) model. It ensures that there aren't any unused places in memory. For a more in depth explenation as to why you can go to this link to find out more
I then create a new function that will create a player. It will be composed of all these components
void create_player(int entity,
float position_x,
float position_y,
float velocity_x,
float velocity_y,
char *path)
{
world->positions->x[entity] = position_x;
world->positions->y[entity] = position_y;
world->velocities->x[entity] = velocity_x;
world->velocities->y[entity] = velocity_y;
world->textures->path[entity] = path;
textureCount = textureCount + 1;
positionCount = positionCount + 1;
velocityCount = velocityCount + 1;
}
the texture, position, and velocity count variables are global variables that I reference later to make looping through all of the data easier
A system is typically a function that loops through the data of component(s) and changes it's data. An example of something you might use as a system would be movement
void movement_system()
{
// This is where keeping track of the number of components can come in handy
for(int i = 0; i < velocityCount; i++)
{
world->positions->x[i] += world->velocities->x[i];
world->positions->y[i] += world->velocities->y[i];
}
}
You then call the function movement_system()
in your main game loop along with any other systems you want to implement
World is a Struct that contains references to each of my Components and all my Entities. It looks like this:
typedef struct {
int entities[MAX_ENTITIES];
Position *positions;
Velocity *velocities;
Texture *textures;
} World;
I create it and allocate each of its fields into memory using this function:
World *create_world()
{
World *world;
world = malloc(sizeof(World));
world->positions = malloc(sizeof(Position));
world->textures = malloc(sizeof(Texture));
world->velocities = malloc(sizeof(Velocity));
return world;
}
And I free it from memory using this function
void destroy_world(World *self){
free(self->velocities);
free(self->textures);
free(self->positions);
free(self);
}
I then declare a World
variable that I reference in the rest of my program
World *world;
In a different file:
// reference to initial world declaration
extern World *world;