Game

ECS Public API

How to use Nevo's entity-component-system from game code.

Core Model

Nevo uses a data-oriented ECS:

  • Entity - a uint32_t ID.
  • Component - a plain data struct.
  • System - logic that runs over entities matching a component signature.
  • Coordinator - the public API for entity, component, and system operations.

Include the public coordinator and component headers:

#include <EngineExports.hpp>
#include <ECS/Coordinator.hpp>
#include <ECS/components/Transform.hpp>

Get the coordinator from the engine:

auto& engine = Public::Engine::getInstance();
auto& coord = engine.GetCoordinator();

Entities

Public::ECS::EntityID entity = coord.createEntity();
if (entity == Public::ECS::INVALID_ENTITY) {
    LOG_ERROR("Could not create entity");
}

Useful entity APIs:

APIUse
createEntity()Allocate a new entity ID.
createEntity(entityID)Create a specific ID, used by scene loading.
destroyEntity(entity)Destroy the entity, remove its components, and remove it from systems.
clearAllEntities()Destroy every active entity.
getEntityCount()Return active entity count.

Components

Register component types before adding them:

coord.registerComponent<MyComponent>();

Add, remove, query, and read components:

coord.addComponent(entity, MyComponent{});

if (coord.hasComponent<MyComponent>(entity)) {
    auto component = coord.getComponent<MyComponent>(entity);
    if (component) {
        component->get().speed = 8.0f;
    }
}

coord.removeComponent<MyComponent>(entity);

getComponent<T> returns std::optional<std::reference_wrapper<T>>, so you can mutate the component through component->get().

For batch work:

for (auto entity : coord.getEntitiesWithComponent<Transform>()) {
    // IDs with Transform
}

for (auto& transform : coord.getComponentView<Transform>()) {
    // Dense component span
}

Systems

Systems inherit from Public::ECS::System and implement:

class SpinSystem : public Public::ECS::System {
public:
    void update(float dt) override;
    void entityAdded(Public::ECS::EntityID entity) override;
    void entityRemoved(Public::ECS::EntityID entity) override;
};

Register the system and set its signature:

auto spin = coord.registerSystem<SpinSystem>();

Public::ECS::ComponentSignature signature;
signature.set(coord.getComponentType<Transform>());
signature.set(coord.getComponentType<Spin>());
coord.setSystemSignature<SpinSystem>(signature);

The system receives entities that contain every component in its signature. The update order is the order systems were registered.

Iterating Entities

Use entityList() for a stable vector-style view when iteration performance matters:

void SpinSystem::update(float dt) {
    for (auto entity : entityList()) {
        // read/write matching components
    }
}

The legacy entities set is still public, but entityList() avoids set iteration overhead.

Custom Scene Components

Scene JSON can create custom game components if you register a creator before engine.Initialize():

inline void CreateSpinComponent(Public::ECS::EntityID entity, const json& data) {
    Spin spin{};
    spin.speed = data.value("speed", 1.0f);
    Public::Engine::getInstance().GetCoordinator().addComponent(entity, spin);
}

int main(int argc, char* argv[]) {
    auto& engine = Public::Engine::getInstance();
    engine.RegisterComponent("Spin", CreateSpinComponent);
    engine.Initialize();

    auto& coord = engine.GetCoordinator();
    coord.registerComponent<Spin>();
}

Then a scene can include:

{
  "type": "Spin",
  "data": {
    "speed": 2.0
  }
}

Built-In Registrations

The engine registers built-in components during initialization, including Transform, MeshRenderer, Camera, Light, ShadowProjector, Animator, Rigidbody, colliders, and post-processing components.

Game code only needs to register its own custom components and systems.