article concerning component-based entity systems, I decided to implement my own for a small 2D game. This kind of design is probably overkill for such a simple project but I think it is a fun exercise and will help me enforce modular software design. In addition to the usual references
on the subject I have also read Sneftel's thread
, weighting the pros and cons of each method presented. I came up with the following requirements for my architecture:
- Type-safety must be preserved (no dynamic_cast<> or worse, static_cast<>).
- Components should be as independent as possible.
- Component and entity creation should be doable dynamically at run time in order to facilitate the integration of a contents editor.
- Communication between components of a same entity should not be done using packed (serialized) events and giant switch cases to determine their type (similar to point 1).
- Entities should be nothing more than a name common to several components (data and behaviour should be factored in components).
On aspect I liked in several implementations of this system type was the presence of subsystems (call them what you will) that manage particular kinds of components. For instance, the RenderSubsystem
will manage RenderComponents
will manage PhysicsComponents
and so forth with other component types. Since the subsystem contains a list of the components it manages, communication between same-type components of different entities is fast and efficient. In my implementation, components are indexed in an associative array using the names of their respective entities. For example, the PhysicsSubsystem
has direct access to all PhysicsComponents
, allowing it to check for collisions between them without having to fetch the components from their owner entities.
Subsystems are also responsible for component creation. In my test implementation, the parameters passed to the component's constructor are hard-coded, but a future improvement will pass an XMLNode*
, or something similar, containing the relevant information needed to create a component.
All subsystems derive from a Subsystem
abstract base class which defines two methods, Update()
. Components, however, don't have a base class. Their managing subsystems are aware of their interface and don't have to use dynamic_cast<>
all the time.
The most tricky part of any component-base design is, in my opinion, the communication between the components forming a single entity. For example, a ScriptComponent
could ask the PhysicsComponent
to move in a certain direction, which in turn should tell the RenderComponent
that it moved to a certain position so that it can be drawn at the correct location on the screen. Most implementations store the positional information in the entities themselves since several components need that information, but one of my requirements was that an entity should not be anything more than a name or an ID.
The main question now is how to implement a communication system while preserving type-safety (1, 4) and component independence (2). The observer design pattern
(or another signal/slot system) is an obvious answer. The problem of "connecting" components to each other while keeping components independent, however, remains.
My solution lies in the use of the mediator design pattern
, which acts as a communication hub for the whole system. The mediator contains lists of listeners for each event type (e.g., Move or Hit) and the methods used to notify them. In order to preserve type-safety, I decided to go Java-style and use *EventHandler
interfaces with abstract Handle*Event()
At creation time, components (implementing all relevant *EventHandler
interfaces) subscribe to the events they can handle through the mediator. There is one Mediator
instance for each entity, so that whenever a component receives an event notification it knows it comes from a component of the same entity.
To take care of the 3rd requirement, I implemented an EntityBuilder
class, which contains a map of Subsystem
pointers indexed by their type. This class has 3 methods: RegisterSubsystem()
is used to associate a concrete Subsystem
subclass to a type string (e.g., subsystems["render"]
contains a pointer to a RenderSubsystem
stores an entity name and creates a new Mediator
is then used to call the Create()
method on the correct subsystem (the component type is passed to AddComponent()
In conclusion, here is a summary of my approach.
- Type-safety is preserved throughout the entire system. There is no casting nor RTTI used anywhere in the system.
- Components are completely independent. They subscribe to a Mediator for events they can handle and wait for them to occur. If no component can fire those specific events, the system still works without any problem.
- Entities (components) can easily be created at run time. The integration of a content editor will be a trivial task.
- Entities can only have one component of each type. This could easily be solved by using a mulimap instead of a map to store components in each subsystem.
- The mediator object can quickly become a monolithic object. This is the main problem I have with this design, but I think it's a fair compromise for the advantages it offers (type-safety, component independence and dynamic entity creation).
I would appreciate your opinion and comments regarding my system and, if I may be so deserving, suggestions as to how it could be improved.
I can post the code if anybody is interested.