A fully dynamic header only Entity Component System for C 20 where components can be added or removed to entities whenever.
A Entity Component System (ECS) is a design pattern that is used to decouple data from logic. It is a way to organize your code in a way that is more flexible and maintainable. It is a common pattern used in game engines, but can be used in other applications as well.
See it as a container that holds entities, and each entity can have one or more components. A component is a data structure that holds data, and a system is a function that operates on entities that has a specific set of components. A system can be seen as a function that takes in a set of components and performs the operation on said components. The ECS container is responsible for keeping track of which entities has which components, and which systems should be run on which entities.
From Wikipedia:
Entity
An entity represents a general-purpose object. In a game engine context, for example, every coarse game object is represented as an entity. Usually, it only consists of a unique id. Implementations typically use a plain integer for this.
Component
A component labels an entity as possessing a particular aspect, and holds the data needed to model that aspect. For example, every game object that can take damage might have a Health component associated with its entity. Implementations typically use structs, classes, or associative arrays.
System
A system is a process which acts on all entities with the desired components. For example a physics system may query for entities having mass, velocity and position components, and iterate over the results doing physics calculations on the sets of components for each entity.
The behavior of an entity can be changed at runtime by systems that add, remove or modify components. This eliminates the ambiguity problems of deep and wide inheritance hierarchies often found in Object-Oriented Programming techniques that are difficult to understand, maintain, and extend. Common ECS approaches are highly compatible with, and are often combined with, data-oriented design techniques. Data for all instances of a component are commonly stored together in physical memory, enabling efficient memory access for systems which operate over many entities.
- Header only library for easy integration.
- Arena memory allocator of the components allocated on the stack.
- Quick iteration over entities due to it being efficiently packed which reduces cache misses.
- Dynamic adding / removing of components.
- Heavy use of templates and concepts to make misuse of library harder and error message clearer.
- Safe concurrent processing of systems.
- Extensive unit testing of library.
- Separate Attributes/Effects system for easy handling of attributes and effects.
Create a container, components it should handle are coded in upfront:
ecs::ECSManager<int, std::string> ecs;
AddEntity an entity to the container:
ecs::EntityID entity = ecs.AddEntity();
AddEntity two components to the entity:
ecs.AddEntity(entity, std::string("Hej"));
ecs.AddEntity(entity, 5);
Or add both entities at the same time:
ecs.AddSeveral(entity, std::string("Hej"), 5);
Or do the two steps, adding a entity and its components, in one call:
ecs::EntityID entity = ecs.BuildEntity(std::string("Hej"), 5);
Fill upp and loop over a system with its active entities that has active components:
#include <ecs-cpp/EcsCpp.h>
ecs::ECSManager<int, float, std::string> ecs;
auto entity1 = ecs.BuildEntity(std::string("Hello"), 1);
auto entity2 = ecs.BuildEntity(2, std::string("Hello"), 5.0f);
auto entity3 = ecs.BuildEntity(3, 5.0f);
// Will match entity1 and entity2
std::string output;
for (auto [strVal, intVal]: ecs.GetSystem<std::string, int>()) {
output = strVal " - " std::to_string(intVal) " ";
}
ASSERT_EQ(output, "Hello - 1 World - 2 ");
float fsum = 0;
int isum = 0;
// Will match entity2 and entity3
for (auto [intVal, floatVal]: ecs.GetSystem<int, float>()) {
isum = intVal;
fsum = floatVal;
}
ASSERT_EQ(fsum, 10.0f);
ASSERT_EQ(isum, 5);
The ECS library also includes a separate system for handling attributes and effects. This system is used to handle attributes and effects in a game, where an attribute is a value that can be modified by effects. An effect is a modifier that can be applied to an attribute. The system is designed to be easy to use and flexible where you define all the attributes and effects upfront. The user of the system builds up and extends it with the effects and attributes they need.
A simple example where we have a basic attribute with one effect that doubles the health of the entity.
#include <ecs-cpp/AttributesEffects.h>
struct HeroAttributes {
float health = 100.0f;
float speed = 1.0f;
};
struct HealthBoost {
float healthBoost = 2.0f;
using Attribute = HeroAttributes;
void Apply(Attribute& attribute) const {
attribute.health *= healthBoost;
}
};
ecs::ECSManager<int, std::string> ecs;
ecs::AttributesEffectsManager<ecs::Attributes<HeroAttributes>, HealthBoost> aem;
auto entity = ecs.BuildEntity(1);
{
auto& attributes = aem.Get<HeroAttributes>(entity);
ASSERT_EQ(attributes.health, 100.0f);
}
{
auto& attributes = aem.Get<HeroAttributes>(entity);
aem.Add(entity, HealthBoost());
ASSERT_EQ(attributes.health, 200.0f);
}
{
auto& attributes = aem.Get<HeroAttributes>(entity);
aem.Add(entity, HealthBoost());
ASSERT_EQ(attributes.health, 400.0f);
}
{
aem.Modify(entity, HeroAttributes{10.0f, 1.0f});
auto& attributes = aem.Get<HeroAttributes>(entity);
ASSERT_EQ(attributes.health, 40.0f);
}
- Clone ecs-cpp to your project.
- Add
add_subdirectory(path/ecs-cpp)
to your CMakeLists.txt. - Link your project to
ecs-cpp
. - Include
#include <ecs-cpp/EcsCpp.h>
in your project.
- C 20
Here you can find a example on how to integrate this library into your project using CMake: ecs-cpp-example. In this example, the library is placed under thirdparty/ecs-cpp.
- Clone ecs-cpp to your project.
- Install dependencies.
- Add
add_subdirectory(path/ecs-cpp)
to your CMakeLists.txt.
- C 20
- GTest
This library uses the PackageManager Conan for its dependencies, and all dependencies can be found in conantfile.txt
.
- Install conan
pip3 install conan
- Go to the build folder that cmake generates.
- Run
conan install ..
see installing dependencies