Description

An open-source D entity-component-system.

Package Information

Version1.0.9 (2015-Sep-23)
Homepage https://github.com/jzhu98/star-entity
Repository https://github.com/jzhu98/star-entity
LicenseMIT
CopyrightCopyright © 2014 James Zhu
Authorsjzhu98
Registered byJames Zhu
Dependencies none

Installation

To use this package, put the following dependency into your project's dependencies section:

dub.json
dub.sdl

Readme

star-entity

An open-source entity-component-system, written in D. star-entity offers component management, entity creation, event delivery, and system management.

This framework is essentially a D port of EntityX by Alec Thomas, with some features from Ashley, managed by Badlogic Games.

Overview

The framework is modeled after the Entity-Component-System (ECS) architecture, a form of decomposition that decouples logic and data, and using composition instead of inheritance to allow greater flexibility and modular functionality.

Essentially, data is condensed into a component, a simple data class, and an Entity is simply an aggregate of these components. Systems encapsulates logic and operates upon a specific subset of entities, namely those with specific components. Events allow for system interaction without tight coupling.

As an example: a game might have players with health, sword, speed, position, bounds and sprite, and walls would have position, bounds, and sprite.

The graphics system would need only the position and sprite components, whereas the physics might require the position and bounds.

If the player collided with the wall, the physics system might emit a collision event.

The article Evolve your Hierarchy offers a great introduction and overview of ECS frameworks and how they can make your code more modular, more extensible, and simpler.

Building

This project uses the DUB build system, found here.

To build the project, simply run in the top-level directory

dub build --build=release

To use this project as a dependency, add the latest version (see Releases) to your dub.json:

"dependencies": {
    "star-entity": "~>1.0.8"
}

Usage

Some example code to implement the aforementioned physics system:

Entities

star.entity.Entity wraps on opaque index (uint) that is used to add, remove, or retrieve components in its corresponding star.entity.EntityManager.

Creating an entity is done by

import star.entity;

auto engine = new Engine;
auto entity = engine.entities.create();

The entity is destroyed by

entity.destroy();
Implementation details:
  • The entity wraps an index (uint) and a tag (uint).
  • Entity acts as a handle, meaning that multiple Entities may refer to the same entity.
  • Entity.invalidate() is used to invalidate the handle, meaning it can no longer be used. The data, however, is still intact and is still accessible.
  • Entity.destroy() is used to invalidate all handles and deallocate the data, freeing the index for reuse by a new entity.
  • Entity.valid() should always be used to check validity before usage.
  • Destruction is done by incrementing the tag; thus making all current Entities tags unequal and invalid.

Components

Components should be designed to hold data, and have few methods (if any). At the moment, they must be implemented as classes (for internal storage), but in the future I hope to implement templates properly to enable using POD structs.

Creation

Continuing our previous example of a physics system:

class Position
{
    this(double x, double y) { this.x = x; this.y = y; }
    double x, y;
}

class Velocity
{
    this(double x, double y) { this.x = x; this.y = y; }
    double x, y;
}

class Gravity
{
    this(double accel) { this.accel = accel; }
    double accel;
}
Assignment

To associate these components with an entity, call Entity.add(C)(C component):

entity.add(new Position(1.0, 2.0));
entity.add(new Velocity(15.0, -2.0));
entity.add(new Gravity(-9.8));
Querying

To access all entities with specific components, use EntityManager.entities!(Components...)():

foreach(entity; engine.entities.entities!(Position, Velocity))
{
    // Do work with entities containing Position and Velocity components
}

To access a specific entity's component, use Entity.component!(C)():

auto velocity = entity.component!Velocity();

Systems

Systems implement logic and behavior. They must implement the star.system.System interface (configure() and update())

Continuing our physics example, let's implement a movement and gravity system:

class MovementSystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(entity; entities.entities!(Position, Velocity)())
        {
            auto position = entity.component!Position();
            auto velocity = entity.component!Velocity();
            position.x += velocity.x * dt;
            position.y += velocity.y * dt;
        }
    }
}

class GravitySystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(entity; entities.entities!(Velocity, Gravity)())
        {
            auto gravity = entity.component!gravity();
            auto velocity = entity.component!Velocity();
            auto accel = gravity.accel * dt;
            if (antigravity)
            {
                accel = -accel;
            }
            velocity.y += accel;
        }
    }
private:
    bool antigravity = false;
}

Adding them to the system manager is quite simple:

engine.systems.add(new MovementSystem);
engine.systems.add(new GravitySystem);

Events

Events are objects (structs or classes) that indicate something has occured, e.g. a collision, button press, mouse event, etc. Instead of setting component flags, events offer a simple way of notifying other classes of infrequent data, using callbacks.

Event types

Events can be either structs or classes. No interfaces or class extension necessary.

struct Collision
{
    Entity first, second;
}
Event emission

Our collision system will emit a Collision object if two objects collide. (Ignore the slow algorithm below without any of that fancy "spatial partitioning". This is just an example.)

class CollisionSystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(first; entities.entities!(Position))
        {
            foreach(second; entities.entites!(Position)())
            {
                if (collides(first, second))
                {
                    events.emit(Collision {first, second});
                }
            }
        }
    }
}
Event subscription

Classes intending to receive specific events should implement the Receiver(E) interface, for events of type E.

class DebugSystem : System, Receiver!Collision
{
    void configure(EventManager events)
    {
        events.subscribe!Collision(this);
    }

    void update(EntityManager entities, EventManager events, double dt) { }

    void receive(E)(E event) pure nothrow if (is(E : Collision))
    {
        try
        {
            debug writefln("Entities collided: %s, %s", event.first.id, event.second.id);
        }
        catch (Throwable o)
        {
        }
    }
}

<sidenote> For those of you who've made it so far: should pure nothrow be enforced upon the receive callback? </sidenote>

A few events are emitted by the star-entity library:

  • EntityCreatedEvent
  • EntityDestroyedEvent
  • ComponentAddedEvent(C)
  • ComponentRemovedEvent(C)

Engine

The engine ties everything together. It allows you to perform everything listed above, and manage your own game / input loop.

while (true)
{
    engine.update(0.02);
}

License

This code is licensed under the MIT License. See LICENSE for the full text.

Available versions

1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.2.1 0.2.0 0.1.0 ~master ~develop