Systems

Systems contain most of the logic. They process and manipulate the components. That means you only have input through a combination of components. These components are obtained through entities. Systems are in essence nothing more than a function.

Template

Systems always need to have the same parameters and a name. A template is included to make it easier to create systems. In the Application project’s include folder you will find example_system.h with the following code. This file can be used for your own systems.

#pragma once
#include <zel_api.h>
#include <zel_math.h>

const char* example_system_name = "example_system";

void example_system_update(zel_level_t* level, float delta_time)
{
        zel_component_collection<zel_transform_t> transform_collection = zel_component_collection_create<zel_transform_t>(level);
        for (size_t component_index = 1; component_index < transform_collection.length; component_index++)
        {
                zel_transform_t* transform = &(*transform_collection.first)[component_index];
                transform->position.x += 1;
                //zel_print("New transform X position | entity [%d]: %0.1f\n", transform_collection.entities[component_index], transform->position.x);
        }
}

Registering a system

Systems needs to be registered just like components. This way you can turn systems on and off when you like. To register a system you also need to input a name, which is used to unregister a system.

zel_level_register_system(example_level, example_system_update, "example_system");

Unregistering a system is done in somewhat the same way.

zel_level_unregister_system(example_level, "example_system");

Click me to learn more

Systems are literally functions with a name attached to them.

typedef struct zel_level_t _zel_level_t;
typedef void(*zel_system_t)(_zel_level_t* level, float delta_time);

zel_system_t is just a function pointer. It gets associated to a string, the system name, when you register a system in a level.

void zel_level_register_system(zel_level_t* level, zel_system_t system_update, const char* system_name)
{
        level->systems.insert({ system_name, system_update });
}

For more information, see the zel_level.h section.

Accessing Components

To access entities and their components in systems there are actual three ways to do it. The key is to think about what data you need, how you access the data and profiling your code is important.

So there is something called entity collection, which is simply a std::vector that contains entities. Then we have the component collection, which is a struct containing pointers to the components. Finally there is the duo collection, which, as the name may suggest, contains entities as well as components.

The easiest collection to use would be the entity collection. It will return a list of entities that have the components you want to access.

std::vector<zel_entity_id> entity_collection = zel_entity_collection_create<zel_transform_t>(level);

You can simply iterate over the entities because it’s a std::vector. Then to access a component you can simply call zel_level_get_component with the entity as parameter.

std::vector<zel_entity_id> entity_collection = zel_entity_collection_create<zel_transform_t, zel_material_t, zel_mesh_t>(level);
for (size_t collection_index = 0; collection_index < entity_collection.size(); collection_index++)
{
        zel_entity_id entity = entity_collection[collection_index];
        zel_transform_t* transform_of_entity = zel_level_get_component<zel_transform_t>(level, entity);
}

As you can see in the code snippet above it is also possible to insert multiple component types. This will return all entities which have exactly that combination of components attached to them.

Accessing Entities in a system

You may want to access a particular entity in a system. For example you only want to process the enemy’s components. If all enemies have an enemy_ai component, you could use that component to identify all enemies. Iterating over those enemies in a system would look like the following.

std::vector<zel_entity_id> entity_collection = zel_entity_collection_create<enemy_ai, zel_transform_t>(level);
for (size_t collection_index = 0; collection_index < entity_collection.size(); collection_index++)
{
        //Only enemies with an enemy_ai and transform component will be processed here
}

Otherwise you could differentiate entities by adding a tag to them. This would just be a component, but we call it a tag because it’s a component without any data.

//Put this at the top of a file
struct enemy_tag { };

//This will go alongside other initialization code
zel_level_register_component<enemy_tag>(example_level);
zel_level_add_component(example_level, entity, enemy_tag);

//This will basically be your system
std::vector<zel_entity_id> entity_collection = zel_entity_collection_create<zel_transform_t, enemy_tag>(level);
for (size_t collection_index = 0; collection_index < entity_collection.size(); collection_index++)
{
        //Only entities with the enemy tag and transform component will be processed here
}