Introduction - GoldhawkInteractive/X2-Modding GitHub Wiki

What is an Entity-Component-System (ECS) Framework?

The Entity-Component-System (ECS) framework is an extension of the Entity-Component framework, defined by three items:

  • Components
    • A single, independent piece of data that contains no game-logic. It will usually contain data objects or primitives along with setters, getters and simple logic normally found in datastructures.
  • Entities
    • An entity is a set of components.
  • Systems
    • A system is logic that operates on a group or groups of entities with the same set of components, which triggers on specific events.

What sets the ECS framework apart from the traditional Entity-Component framework (such as Unity) is the separation of data from logic.

Components no longer contain logic, as all logic is defined in Systems. The benefit of this is that the behavior of an Entity can be changed by simply switching out the Systems which operate on this Entity.

What sets Artitas apart from other frameworks is the introduction of events, examples of events you can subscribe to are:

  • Changes in values in Components. (E.g.: When Health becomes 0, kill the entity.)
  • Adding or Removing components on entities. (E.g.: When the Ammo component is attached to an entity, show a UI Label.)
  • Deleting or creating entities in the world, or once they have or lack specific components. (E.g.: When the Boss entity is deleted, display the victory screen.)
  • Arbitrary Events queued in the world. (E.g.: KeyPressed, ProjectileImpact or PlayerLost)

The following text is courtesy of Artemis-ODB, but altered a bit for our flavor of ECS.

Can you give me an example?

Let's take a simple platform game where you have characters that move around. In this hypothetical example, you'll need ways of dealing with the character's game physics, drawing the character, and driving how the character moves. To build this, we could start with the following components:

  • Motion, for driving how the character moves.
  • Spatial, for drawing how the character appears.
  • Physics, for maintaining physical information about the character for collision handling, applying physical forces, etc.
  • PlayerInput, for defining player input.

From this, we'd define a "player entity" and then add those components at run-time.

Next, we create systems to act on the various components. Our systems could be:

  • RenderSystem, which acts on any entities that contain Physics and Spatial components, and listens to frame updates. We utilize the Physics component to determine where in the physics space the character is. We utilize the Spatial component to determine how to draw the character. If an entity does not have both of these components, then it will NOT be processed by RenderSystem.

  • MotionSystem, which acts on any entities that contain Physics and Motion components, and listens to changes on Motion. We utilize the Motion component to determine what motion effects are applied to an entity (left, right, jump, etc.) We utilize the Physics component to apply forces to the entity based on Motion. As above, if an entity does not have both of these components, then it will not be processed by MotionSystem.

  • PlayerInputSystem, which acts on any entities that contain Motion and PlayerInput components, and listens to InputEvents. The player input component could contain info about whether the related input is "locally" or "remotely" (via network or some other means) defined. For a "local" player entity, keypresses update the Motion component with information. For a "remote" player entity, network commands update the Motion component with information.

An example flow of this architecture would be: local player presses the "move left" key. PlayerInputSystem is subscribed to any InputEvent and executes, updating the Motion component. MotionSystem responds to changes on the Motion component, and applies a Physics force to the left.

Now, the World receives an update to trigger the next frame. The RenderSystem responds this, executes and reads the current position of the entity, and draws it according to the Spatial definition (which may include texture region / animation information).

Ugh. Seems like a lot of work. Why not use standard OOP?

One of the advantages of the Entity System is that it helps decompose or compartmentalize things. You have code that operates on very specific things and does this regardless of other components that the entity might have. Further, it helps prevent from things degenerating into the "blob" anti-pattern. As the game architecture becomes more complex, you simply add or remove components without having to worry about massive refactoring.

In a hierarchical approach, if two classes in very different places of the hierarchy tree require commonality, they'll either need to be derived from the same super class which takes on the shared qualities or copying and pasting will be required. This is fine for shallow trees, but games tend to evolve over time turning this into a potential chore.

Decomposing the architecture using an entity system gives us a very easy path for adding / extending / altering operation. Let's say in our example above, we now wanted to create some enemies. Rather than deriving or cutting and pasting things, we create a new component: EnemyAI.

We then create a new entity that consists of: Motion, Physics, Spatial and EnemyAI component, and we create a new EnemyAISystem that operates on any entity with the EnemyAI, Physics, and Motion components. This system implements enemy behaviors that define when and how to move the related entity, updating the Motion component in the process.

Our RenderSystem and MotionSystem will pick up new enemy entities with no further changes required.

⚠️ **GitHub.com Fallback** ⚠️