Question
A component based system's goal is to solve the problems that derives from inheritance: for example the fact that some parts of the code (that are called components) are reused by very different classes that, hypothetically, would lie in a very different branch of the inheritance tree.
That's a very nice concept, but I've found out that CBS is often hard to accomplish without using ugly hacks. Implementations of this system are often far from clean. But I don't want to discuss this any further.
My question is: how can I solve the same problems a CBS try to solve with a very clean interface? (possibly with examples, there are a lot of abstract talks about the "perfect" design already).
Context
Here's an example I was going for before realizing I was just reinventing inheritance again:
class Human {
public:
Position position;
Movement movement;
Sprite sprite;
// other human specific components
};
class Zombie {
Position position;
Movement movement;
Sprite sprite;
// other zombie specific components
};
After writing that I realized I needed an interface, otherwise I would have needed N containers for N different types of objects (or to use boost::variant to gather them all together). So I've thought of polymorphism (move what systems do in a CBS design into class specific functions):
class Entity {
public:
virtual void on_event(Event) {} // not pure virtual on purpose
virtual void on_update(World) {}
virtual void on_draw(Window) {}
};
class Human : public Entity {
private:
Position position;
Movement movement;
Sprite sprite;
public:
virtual void on_event(Event) { ... }
virtual void on_update(World) { ... }
virtual void on_draw(Window) { ... }
};
class Zombie : public Entity {
private:
Position position;
Movement movement;
Sprite sprite;
public:
virtual void on_event(Event) { ... }
virtual void on_update(World) { ... }
virtual void on_draw(Window) { ... }
};
Which was nice, except for the fact that now the outside world would not even be able to know where a Human is positioned (it does not have access to its position member). That would be useful to track the player position for collision detection or if on_update the Zombie would want to track down its nearest human to move towards him.
So I added const Position& get_position() const; to both the Zombie and Human classes. And then I realized that both functionality were shared, so it should have gone to the common base class: Entity. Do you notice anything? Yes, with that methodology I would have a god Entity class full of common functionality (which is the thing I was trying to avoid in the first place).
Meaning of "hacks" in the implementation I'm referring to
I'm talking about the implementations that defines Entities as simple IDs to which components are dynamically attached. Their implementation can vary from C-stylish:
int last_id;
Position* positions[MAX_ENTITIES];
Movement* movements[MAX_ENTITIES];
Where positions[i], movements[i], component[i], ... make up the entity. Or to more C++-style:
int last_id;
std::map<int, Position> positions;
std::map<int, Movement> movements;
From which systems can detect if an entity/id can have attached components.