ECS developpé dans le contexte de Re-Type™. Intègre des fonctionnaltiés communes pour la gestion de composants, de systèmes et d'events. Lourdement inspiré de EnTT.
Fournir une base performante, la plus simple d'utilisation possible pour composer tout type de jeux via une architecture articulée autour d'un ECS.
Concrètement, on peut distinguer 3 "niveaux" dans la structure de l'ECS:
- SparseSet(s)
- Registry
- View(s)
Chaque niveau étant responsable à son échelle du bon fonctionnement général.
Les SparseSets sont des container permettant l'insertion et la récupération de donnée en complexité O(1), et facilitant l'ittération en complexité O(n).
Le registry est l'orchestrateur de l'ECS, il stocke les SparseSet ainsi que les systèmes, expose les fonctions nécessaires à la création d'entités, l'ajout de composants et de systèmes.
Les Views facilitent l'ittération d'un ensemble de composants simultanément. C'est la méthode privillegiée pour effectuer des opérations sur les entités. Elles sont adaptée à l'usage des transformateurs de std::view.
Donnons l'example simple de la création d'un système de velocité. On veut que chaque entité possedant une Position et une Velocité, se déplace sur chaque axe en fonction de cette dernière.
Si on procède de zéro, cela donne:
#include "raccEngine/core.hpp"
// Components
struct Position {
float x;
float y;
};
struct Velocity {
float x;
float y;
}
// VelocitySystem.hpp
class VelocitySystem : public ISystem {
public:
VelocitySystem(Registry& registry);
~VelocitySystem() = default;
void update() override;
private:
Registry& _registry;
};
// VelocitySystem.cpp
#include "components/position.hpp"
#include "components/velocity.hpp"
VelocitySystem::VelocitySystem(Registry& registry) : _registry(registry) {}
void VelocitySystem::update() {
auto view = _registry.view<Velocity, Position>();
// Récupération des entités ayant une vélocité non nulle.
auto filtered = view | std::ranges::views::filter([&view](const auto entity) {
auto [velocity, _] = view.get(entity);
return velocity.x != 0 || velocity.y != 0;
});
// Ittération de l'objet View donne des "Entity" (id d'entités)
for (auto entity : filtered) {
// Récupération des composants demandés, pour chaque entité
// (sous forme d'un tuple de références)
auto [velocity, position] = view.get(entity);
position.x += velocity.x;
position.y += velocity.y;
}
}
// main
int main(void) {
Registry registry;
bool running;
registry.registerComponent<Position>();
registry.registerComponent<Velocity>();
registry.registerSystem<VelocitySystem>(registry);
auto ent = registry.create();
registry.addComponent<Position>(ent, Position{0.f, 0.f});
registry.addComponent<Velocity>(ent, Velocity{10.f, 0.f});
while (running) {
registry.update();
}
}
Par défaut, la méthode update se déclenchera 128 fois par secondes. Si l'on souhaite plutôt qu'un système s'actualise à chaque cycle de boucle, il faut le register comme "unfixed":
registry.registerUnfixedSystem<RendererSystem>(registry, renderer);
Le registry intègre également une gestion de la parentalité des entités via les méthodes:
void setParent(Entity child, Entity parent);
Entity getParent(Entity child) const;
std::vector<Entity> getChildren(Entity parent) const;
Globalement, pour les détails de chaque fonction il est recommandé de consulter leur documentation technique afin d'en comprendre finement le fonctionnement.