Critique my component-based entity system

Started by
14 comments, last by theOcelot 14 years, 10 months ago
Rather than 'static data', you might want to think of it as 'prototype data'. :)
Advertisement
Quote:Original post by Zahlman
Rather than 'static data', you might want to think of it as 'prototype data'. :)

We call it "type data", but yes another confusing thing about component systems is the varying terminology between designs :)
I am surprised no one has mentioned the Command pattern and the Visitor pattern. According to most of the engines I have seen, these are the most common ways of implementing interactions between objects while maintaining complete separation. XezekielX, you might want to look into these as well as Observor as a way of avoiding a large monolithic system that could result from the Mediator pattern. My understanding is that this pattern is necessary only for objects where the interactions between them will be very complex and you want to encapsulate the logic externally to the objects. But most interactions between objects will be simple and straightforward such as passing data or invoking the functionality of one object from another.
In particular I have been studying behavior tree engines which make heavy use of Command, Visitor, Observor, as well as Decorator, and State patterns. The whole system is based on callable entities a.k.a Functors or delegates. I imagine the same techniques could be useful in any kind of engine. The book Modern C++ Design with the Loki library is a good reference.

But like you I am just a beginner with implementing OO engines and I do not have alot of hands-on coding experience since I am still in the research and design phase of my own system. I do not have alot of time to work on it since my current job is all J2ME game programming and none of the programmers at my company have a clue about how to write code. Thank goodness for the GameDev forums :)
But I would be quite interested in knowing how things go with your implementation as I could probably pick up alot of good tips from it. I hope you keep us posted on your progress and I hope my suggestions are of some help.

Thanks.
So we have a bag-of-components style Entity. The components are typesafe because, by the time that their interface has been lost, they do all their communication with message passing. The Entity serves as a message bus between the components, and little more.

I think you should strike a balance between packed events and named event handler functions. Instead of HandleCharactersHelmetHitByLargeFlightlessBirdEvent, have a HandleHitEvent(HitEvent&) function, where HitEvent has info about where the hit was, and what delivered it. You can design the interactions of the game so that there are a limited number of these event types, which are then all that the Mediator has to deal with. Also, this way you aren't as limited by the event functions that are built into the Mediator interface when designing new interactions; new interaction types can be created at runtime.

While I'm on the topic, you may as well merge the Mediator and Entity classes. After all, when are they ever used independently of each other?

In my initial summary, I almost said something like "by the time the components have been upcasted to their common interface for storage" until I remembered that your components don't have a common interface. How are the components stored in a map, if they don't have a common base class?
Quote:Original post by theOcelot
I think you should strike a balance between packed events and named event handler functions. Instead of HandleCharactersHelmetHitByLargeFlightlessBirdEvent, have a HandleHitEvent(HitEvent&) function, where HitEvent has info about where the hit was, and what delivered it. You can design the interactions of the game so that there are a limited number of these event types, which are then all that the Mediator has to deal with. Also, this way you aren't as limited by the event functions that are built into the Mediator interface when designing new interactions; new interaction types can be created at runtime.
This is exactly what I have at the moment. The Hit event handler, since you mention it, receives a CollisionData structure (not sure what to put in it right now, as I am still in the early stages of development), the Move event handler receives the direction in which to move, etc.

Here is the event graph for my game. There are only 5 events so far; more will certainly be added (same with components) but I am confident I will be able to keep them generic enough to avoid HandleCharactersHelmetHitByLargeFlightlessBirdEvent.

Quote:Original post by theOcelot
While I'm on the topic, you may as well merge the Mediator and Entity classes. After all, when are they ever used independently of each other?
Not quite sure what you mean here. In my implementation there is no Entity class; entities are nothing more than a name (or an ID).

Quote:Original post by theOcelot
In my initial summary, I almost said something like "by the time the components have been upcasted to their common interface for storage" until I remembered that your components don't have a common interface. How are the components stored in a map, if they don't have a common base class?
The RenderSubsystem has a map of RenderComponents, the PhysicsSubsystem has a map of PhysicsComponents, etc. Each component type is associated with a subsystem that manages it. This basically means that each time I want to implement a new type of component I also need to implement a new type of subsystem.

While components don't share a common interface, subsystems do. The Subsystem interface exposes 2 methods, Create() and Update(). Create() is used to instantiate a component and Update() updates the the subsystem (which can choose to update some, all or no components at all).

I might implement a generic subsystem using templates to reduce the number of concrete subclasses but so far they are all different enough from each other to warrant a specific implementation for each.

Quote:Original post by gtdcoder
I am surprised no one has mentioned the Command pattern and the Visitor pattern. According to most of the engines I have seen, these are the most common ways of implementing interactions between objects while maintaining complete separation. XezekielX, you might want to look into these as well as Observor as a way of avoiding a large monolithic system that could result from the Mediator pattern. My understanding is that this pattern is necessary only for objects where the interactions between them will be very complex and you want to encapsulate the logic externally to the objects. But most interactions between objects will be simple and straightforward such as passing data or invoking the functionality of one object from another.
The Mediator pattern is actually implemented with the Observer pattern. It contains a list of listeners and notifies them when a certain event occurs (although the notifications are raised by the components themselves).

The main reason why I used the Mediator pattern instead of the simpler Observer pattern (where lists of listeners would have been in the "subject" components) was to enable the components to listen to events on each other without actually knowing about each other. If I had used the Observer method, I would have had to do something like
entity->GetComponent<PhysicsComponent>()->ListenToHitEvent(this);
which poses an important problem: The components aren't completely independant. If I decide to split the PhysicsComponent into two or more finer-grained components, I will also have to change all components that listen to event raised by PhysicsComponents. This will not be a problem in the Mediator-based implementation because senders, handlers and events are completely separated from each other.

As a side effect, events can also be invoked from different components. If you look at the event graph I linked earlier in this post, you can see that both the InputComponent and ScriptComponent raise the Shoot and Move events. Instead of having two separate lists of listeners for each of these events, there is only one list for the Shoot event handlers and one list for the Move event handlers (per entity, of course). I believe that in a component-based entity system, components should not care where an event comes from but only that it did happen. Now, it doesn't make much sense for an entity to both have an InputComponent and a ScriptComponent but the point is that the system would still work if it were the case. Components can listen to the events they want without any knowledge of the presence (or absence) of other components, regardless of where the events came from.

As for the Command pattern, it cannot be used in this system (or rather, I could not find a way to) simply because events/actions/commands need arguments at execution time. The Command pattern can only encapsulate arguments at creation time.
it seems rational thought has been killed and buried
Quote:Original post by XezekielX
Quote:Original post by theOcelot
While I'm on the topic, you may as well merge the Mediator and Entity classes. After all, when are they ever used independently of each other?
Not quite sure what you mean here. In my implementation there is no Entity class; entities are nothing more than a name (or an ID).


Ah, one of those type of entity systems. Must have missed that.

Quote:
Quote:Original post by theOcelot
In my initial summary, I almost said something like "by the time the components have been upcasted to their common interface for storage" until I remembered that your components don't have a common interface. How are the components stored in a map, if they don't have a common base class?


The RenderSubsystem has a map of RenderComponents, the PhysicsSubsystem has a map of PhysicsComponents, etc. Each component type is associated with a subsystem that manages it. This basically means that each time I want to implement a new type of component I also need to implement a new type of subsystem.


That all makes sense now. Thanks for explaining.

This topic is closed to new replies.

Advertisement