I've recently been making an entity system. Initially, I was planning it around a component-based model:
class Component {
public:
virtual void OnUpdate() = 0;
...
};
class Entity {
...
std::vector<shared_ptr<Component> > components;
...
}
In this model, the Entity aggregates a list of heterogeneous components, which modify the behavior of the object without having to complicate the Entity class. The factory in charge of creating Entities is responsible for populating them with the appropriate Components.
There have always been a few things I didn't like about the component-based model.
* Components have difficulty finding other components in the same Entity. Given an AnimationComponent which requires a PathfindingComponent to use, often it's necessary to manually iterate through the list of Components, dynamic_casting each one until you get a hit, or else to complicate the factory with details of what component needs to know about what, or the Entity with specific typed Component members for direct access.
* Components have difficulty finding the same components in other Entities, and their associated game-wide subsystems. The PathfindingComponent needs to know at least about other Entities' PathfindingComponents, or (more likely) a GameWidePathfindingSystem. The former can require a lot of iteration and dynamic_casting; the latter requires either some pretty serious arrow chains, or singletons/globals.
The basic problem, I think, is that an Entity has a big ol' list of components and no clue what to do with them. I've shown an Update event up there, but even that's a stretch; I've seen a lot of components with empty OnUpdate() methods. You can, of course, add OnDamage and OnNewPath and OnEntityScratchedLeftEar as necessary, but that makes the component-entity response matrix sparser and sparser. You can abandon that approach and have entities hook into the events they care about directly, but then what's the point of the observer pattern there at all?
While designing my graphics system (this time through, enforcing a really strong logic/graphics separation), I fell into this alternate arrangement.
class FooComponent
{
FooComponent(shared_ptr<Entity> entity);
...
};
class FooSubsystem
{
void OnEntityAdded(shared_ptr<Entity> entity)
{
components[entity] = SomeComponent::Create(entity);
}
void OnEntityRemoved(shared_ptr<Entity> entity);
private:
hash_map<shared_ptr<Entity>, shared_ptr<SomeComponent> > components;
};
class World
{
Event<shared_ptr<Entity> > EntityAdded;
Event<shared_ptr<Entity> > EntityRemoved;
};
I realize that's a bit skeletal, but what's going on there is that the subsystem hooks into events which fire when entities are added to or removed from the world, automatically creating corresponding components for itself and holding onto them. The same components as before are being created; but rather than being stored by the Entity in heterogeneous lists, they're stored by their parent subsystem in homogeneous lists.
The thing I like most about this is that there's no need for the components to swim back to their subsystem. In fact, they generally don't need to know about the subsystem at all. More subtly, in the case where one component type (BarComponent, say) depends on another (FooComponent), the BarSubsystem can listen to the FooSubsystem for add/remove events instead of the World, automatically matched up to the component it cares about.
In the case of the entity/graphics divide, doing something like this is just common sense. I've been using it for a lot more, though; pretty much all of the behavior of my Entity, which by now is a pretty teeny class. I'm sure others have explored this organization before. What say you all?
[Edited by - Sneftel on September 10, 2007 1:12:22 PM]