Skip to content

Entity Component System

Matt Styles edited this page Apr 25, 2019 · 2 revisions

At the heart of xygine's Scene is the ECS, or entity component system.

Entities

Entities represent a single game object within the Scene defined by a collection of components which hold the data that stores its properties. Each entity is unique to a Scene, and must be created with the factory function xy::Scene::createEntity(). Entities exist until they are explicitly destroyed with xy::Scene::destroyEntity() or the Scene itself is destroyed.

xygine memory manages entities and they are quick and reliable to create and destroy at runtime. A hard limit of 1024 active entities exists in xygine, but this in practice has proven ample, provided that expired entities are properly disposed of.

They should not be confused with the xy::Entity class, however, which is merely a handle to an entity within a Scene. The entity class is very lightweight, can be copied around, reassigned and even nullified. None of this affects the underlying entity itself unless the accessor functions addComponent<T>() or getComponent<T>() are used to mutate the entity's data.

Components

Components are groups of data which are selectively composed on an entity to define its properties. The most basic components are simple structs of data, although some components have an interface made up of accessor functions for the sake of encapsulation. No logic is contained within a component, as this is the job of a System, and multiple systems may do work on a single component. For example the Transform component, used to define the position, rotation and scale of an entity is read and written by multiple systems when processing the entity.

Components can only be added to an entity immediately after it is created, and they cannot be removed. This is not to say entities cannot be created at runtime - they can - but they must be immediately fully composed, and will remain so until they are destroyed.

Simple sprite or text entities would be composed like so:

auto entity = scene.createEntity();
entity.addComponent<xy::Transform>();
entity.addComponent<xy::Drawable>();
entity.addComponent<xy::Sprite>(mySpriteTexture);

entity = scene.createEntity();
entity.addComponent<xy::Transform>();
entity.addComponent<xy::Drawable>();
entity.addComponent<xy::Text>(myFont).setString("Hello!");

Sprites and text are built in xygine components, but components can be created from any data structure allowing for rich custom behaviour and appearance while maintaining a clean interface.

struct MyThing final
{
    float speed = 100.f;
    sf::Vector2f velocity;
};

entity.addComponent<MyThing>();

xy::Entity::addComponent() returns a reference to a newly created component. Components can also be retrieved with xy::Entity::getComponent(), but do make sure the requested component type has actually been added to the entity first. Trying to access a non-existent component will cause an assertion error in debug builds, but in release builds is undefined behaviour and a cause of subtle bugs. xy::Entity::hasComponent() can be used to check if a component exists.

When accessing components from within a System some guarantees for which components exist can be made to prevent extraneous checks however. See Systems, below.

Systems

Systems are home to the logic which operate on an entity, creating or modifying its behaviour, or updating its appearance. Systems are added to a Scene on initialisation, and should not be added anywhere else. Systems can be temporarily disabled, however, using xy::Scene::setSystemActive<T>(bool).

Systems are added to a Scene with addSystem<T>(). Every system requires a reference to the active MessageBus as a parameter.

auto& messageBus = getContext().appInstance.getMessageBus();
scene.addSystem<xy::TextSystem>(messageBus);
scene.addSystem<xy::RenderSystem>(messageBus);

Every frame in which the Scene is updated each active System is processed, in the order in which they were added to the Scene.

Custom systems can be created by inheriting xy::System. They have multiple virtual functions of which process() at the very least must be implemented. The most basic custom system might look like

MySystem::MySystem(xy::MessageBus& mb)
  : xy::System(mb, typeid(MySystem))
{
    requireComponent<xy::Transform>();
    requireComponent<MyThing>();
}

void MySystem::process(float dt)
{
    auto& entities = getEntities();
    for(auto entity : entities)
    {
        //process entity components
    }
}

The constructor MUST take a reference to xy::MessageBus at the very least. It may take other parameters too, which can be passed via Scene::addSystem(). The System base class must have the message bus reference forwarded to it, as well as the typeid of the custom class.

The constructor body is used to define which components an entity must have for it to be available to the system. An entity may also have other components, but is guaranteed to have at least the components types added with requireComponent().

The member function getEntities() will return a vector of entities currently active in the system. It can then be iterated over as it is processed.

For a complete example of a custom system view the tutorial in the repository, or the demo application.

Stock Components and Systems

TODO.

Clone this wiki locally