Entities, Components, and Systems
To start I've created a C# class library solution to store the basic class and interfaces for the implementation of the ECS pattern. It contains the Entity class, which consists of an Id value and a collection of components. The components implement IComponent, an interface that describes the common behaviours of all component classes. Likewise the systems all implement the interface ISystem, which outlines a set of methods to ensure all of our systems are fulfilling their appropriate role in a given program.
Entity is a very simple class. As I stated in the first entry on ECS, entities are really just an ID value and a set of components, as seen here:
|Entity - An ID with a List Of Components|
In addition to a constructor and properties the Entity class also contains a few utility methods for getting and setting Components in a couple of different ways. The interfaces are fairly simple as well, each only declaring a few methods, here's IComponent:
|IComponent - All Components will have Activate, Deactivate, and Update methods|
|ISystem - All Systems register their components of interest from a set of entities and update those components on iteration|
These three building blocks are part of a solution called "RushECS" that can be downloaded from the GitHub repository linked at the end of this post. The project builds a C# class library DLL file that can also be found in the repo and downloaded for use in any projects you so desire. Before I get into the actual implementation of this library, I'd like to talk briefly about how these pieces are meant to interact...
Avoiding Component Instantiation During Runtime
It's important to avoid making a habit of constructing and destroying objects in the midst of execution if a game is to run at a reasonable pace. Having your framerate slow down randomly because memory is being reallocated unnecessarily can make games look janky and poorly made. This is why IComponent contains a method declaration for Activate and Deactivate, rather than simply having a constructor in the implementing class handle those responsibilities. This doesn't prohibit writing a constructor aside from the default, but it does provide a clear pattern to implement the management of Component states during runtime without eating up any extra RAM.
IComponent contains one other method, Update. This is the other behaviour common to all Components and it's used to expose the specific functionality of a given implementing object to it's corresponding system. In the example to follow, an InputComponent's Update method will be used to notify the InputSystem of any Key presses that map to a set of commands.
Obtaining and Iterating Through a Component Collection
The way ISystem's methods are laid out is a combination of essential system behaviour and accomodating my preferred order of object instantiation when the program is starting up. I want to create all of my entities first, then add components to them, and then finally to register those components with the appropriate systems. As such, systems built using this library register components by going over a collection of Entity objects and checking each for the components they're interested in. This also affords the ability to give the system an association between the entities and their components during gameplay without having to check our entities over and over.
In any implementation of ISystem, there will need to be some determination of how frequently to run the UpdateComponents method. That's how a system regulates it's components behaviour and it's important to ensure that each system is updating with an appropriate frequency, such as a graphics system having a frequency of update that ensures smooth image rendering.
The implementation example below isn't going to have anything as complex as rendering graphics though, as it's just meant to demonstrate how Entities, Components, and Systems can work together to give a strong design base for a game.
The Example: Input and Display
The basic structure of the example project (which can be found in it's entirety on the GitHub repo mentioned earlier) is to run a console application that will receive specific key presses using InputComponents, these components then notify the InputSystem using a ReceivedInputEvent, and the system in turn notifies any other components that would be concerned with that input using a DispatchCommand method. In this case there's only going to be one other type of component to notify, a DisplayComponent, which will put messages into a queue to be printed to the console by the DisplaySystem during update iterations.
Looking at the actual execution of the program (found in Program.cs) you can see what I described earlier; several Entity objects are created and are each given a pair of InputComponents and DisplayComponents, followed by the instantiation of an InputSystem and a DisplaySystem. The entities are grouped in a List and passed into each system's RegisterComponents method, as shown below:
|The first portion of the example's runtime code|
The components then call Activate in turn and the systems are started, followed by an intentional infinite loop to just keep the program running until it's closed.
The Systems and Components During Runtime
When the InputSystem registers a component, it attaches a reference to a method called DispatchCommand, as you can see in the RegisterComponents method here:
|The RegisterComponents method in InputSystem|
During the aforementioned infinite loop, the systems both have a Timer object running that calls their UpdateComponents method according to an Elapsed event. These systems are really simple, so the only thing happening in their UpdateComponents methods are direct calls to the Update methods of their component lists. When InputSystem executes an update on InputComponent, there's an attempt to get any key input from the console, which is then compared against the set of InputCommands specified in the component's builder methods seen in Program.cs. In the event that the key entered matches one of the keys in the component, a ReceivedInputEvent is raised:
|The Update method in InputComponent|
Which then triggers the DispatchCommand method...
|The DispatchCommand method in InputSystem|
In this instance all DispatchCommand has to do is format a message string and send it off to a DisplayComponent, which it finds using the supplied Entity ID value. In a real world implementation there are a plethora of other potential command objects that could be created and sent off to components, because an input system and it's components are responsible for communicating player action into game world effect. For the example though, it sends off a message using QueueOutput which adds the message to a List called OutputQueue to await processing by the Update method, with the oldest messages being displayed first:
|The Update method in DisplayComponent|
So that's it in a nutshell, the program features an Entity Component System architecture composed of:
- Entities - An ID value and a set of Components
- Components - An Object mapped to Entities which define various properties and behaviours
- Systems - An Object which iterates over a set of Components defined by a logical domain and manages their behaviours
The Entity objects each have an InputComponent, which watches for keyboard inputs and notifies the InputSystem, which in turn passes off display instructions to a related DisplayComponent and the message is then queued up for printing out to the console. In action it looks like this:
|Runtime execution of the ECSInputExample project|
I hope this is a helpful breakdown of the Entity Component System pattern. If you have any questions or comments feel free to leave them in the comment section below or contact me on twitter @AdamBoyce4.
As promised earlier the GitHub repository for the class library and example project can be found by following this link. Thanks very much for reading and I hope you'll come back for the next entry.