Skip to content

Gestalt Entity System Quick Start

Immortius edited this page Dec 8, 2019 · 20 revisions

Entity Basics

The core elements of an entity system are:

Entities are identified objects. They have no behaviour or data of their own beyond their identifier, but are composed of Components.

Components are data objects that define the state of an entity. Each component implies a behaviour - a Physics component would imply the entity is affected by physics, a Sprite component would imply it is something that can be rendered in 2D, a Health component would imply the entity can be damaged. Each component then contains configuration and state about that behaviour - a health component may define the maximum health the entity can have (configuration) and its current health (state).

Systems process entities and components in order to produce desired behaviour. There's no strict standard for what a system looks like, although a typical system may process all entities with a particular type of component or set of components every frame. For example, a health regeneration system may iterate all entities with a Health component each frame, restoring a small amount of missing health based on a regeneration rate on that component.

Events are signals that can be sent against an entity. Systems can register to react to events based on the types of components the receiving entity has. For example, a Damage event could be sent against an entity when a situation would cause it to be damaged - such as landing at high speed or being shot. A system could subscribe to this event for entities with a Health component in order to apply that damage to the entity. Another system could subscribe to the event to make a damage sound based on a EventAudio component.

Prefabs are recipes for creating entities. A prefab may be for a single entity, or for a collection of related entities. For example, a vehicle might be comprised of a main entity for the vehicle, an entity for each wheel and an entity for an attached turret.

Component types

Components types are defined as classes implementing the Component interface.

public final class HealthComponent implements Component<HealthComponent> {
  private int maxHealth = 100;
  private int health = 100;

  public HealthComponent () {
  }

  public HealthComponent(HealthComponent other) {
    copy(other);
  }

  private int getMaxHealth() {
    return maxHealth;
  }

  private void setMaxHealth(int value) {
    this.maxHealth = value;
  }

  private int getHealth() {
    return health;
  }

  private void setHealth(int value) {
    this.health = value;
  }

  public void copy(BasicComponent other) {
    this.maxHealth = other.maxHealth;
    this.health = other.health;
  }
}

Component classes must implement a copy method. They may optionally implement a copy constructor in addition to an empty constructor. The copy method is very important - gestalt entity system copies components into its entity stores and then copies them back out when requested, so any value that isn't copied will be lost. Copies should be deep, so that components do not end up sharing references to mutable objects.

Some general rules when implementing components:

  • Component should generally not inherit other components. For instance, you should not have a SpherePhysics component inheriting a Physics component. Rather, you would have a SphereCollider component and a Physics component, and behaviour would be driven by an entity having both. Remember entity systems are a compositional approach, not inheritance driven.
  • Components should not reference other components. They may reference entities (via EntityRef) and component types (via Class<? extends Component>).

Setting up the entity system

To set up the entity system, first you will want to create a component store for each type of component the entity system should support. There are two main choices for component store:

  • ArrayComponentStore is a high performing component store, but uses memory based on the total number of entities, regardless of whether they have a component or not. It should be used at least for all component types that are frequently present on entities, and potentially for other component types if memory is not an issue.
  • SparseComponentStore has potentially lower performance, but a much smaller memory footprint in situations where a component has less frequent use. It should be be used for component types which are less commonly used.

Additionally both types of component store can be wrapped in a ConcurrentComponentStore - this provides thread safety for read and write operations across threads. Note that this does not protect against lost update situations where a thread reads a component and then later writes it without knowing it has been changed by another thread in the meantime - that would require some sort of locking or transaction system which is not provided by gestalt-entity-system.

If using gestalt-module, it is possible to discover all component subtypes from the ModuleEnvironment.

ComponentManager componentManager = new ComponentManager();

List<ComponentStore<?>> stores = Lists.newArrayList();
for (Class<? extends Component> componentType : environment.getSubtypesOf(Component.class)) {
  stores.add(new ConcurrentComponentStore(new ArrayComponentStore(componentManager.getType(componentType))));
}

EntityManager entityManager = new CoreEntityManager(stores);

Working with entities

An entity can be created with the entity manager:

EntityRef entity = entityManager.createEntity();
EntityRef entityWithComponents = entityManager.createEntity(component1, component2, ...);

Entities are generally referenced by an EntityRef. This provides functionality to retrieve, set or remove components:

entity.setComponent(new LocationComponent());
HealthComponent health = entity.getComponent(HealthComponent.class).orElse(null);
entity.removeComponent(HealthComponent.class);

Components are copied in or out of entities - changes to components never affect an entity until they EntityRef::setComponent is used to update it. For maximum performance when working with many entities it is best to reuse components:

LocationComponent location = new LocationComponent();
for (EntityRef entity : entities) {
  entity.getComponent(location);
  // location is now a copy of entity's location
  location.setY(location.getY() + 10f);
  entity.setComponent(location);
  // entity's location is now updated
}

Entities can be destroyed using EntityRef::delete. When this is called:

  • All components are removed from the entity.
  • The EntityRef is marked as deleted.
  • The entity's id is freed up for reuse.

It is important to reference entities using their EntityRef rather than id, as this allows the reference to be invalidated when the entity is destroyed - otherwise there will be issues when the id is reused.

The existence of an entity can be checked for using EntityRef::exists

if (entity.exists()) {
  update(entity);
}

Otherwise a destroyed entity will act as an entity with no components and ignore attempts to alter it.

NullEntityRef

Iterating entities

Sending events

Setting up event handlers

Lifecycle events

Prefabs

Concurrency

Performance