Behaviour/Component design
Posted: Wed Apr 18, 2012 10:37 pm
The concept of Behaviours, or Components, is an alternative to inheritance-based construction of an entity in a game engine. AIGD chapter 17, part 2 (that sounds almost biblical) has a good explanation about this. Basically, what you have is a base Entity class which has references to behaviours that define how it acts, instead of inheriting from a class (which may inherit from another, which may inherit from another, etc). Inheritance is static and difficult to change at redesign-, compile- and run-time.
The common form of a behaviour-based engine is to have behaviour classes to represent aspects of the entities in the game, so:
And so on. I'm keeping the interfaces simple, so it doesn't cloud the main subject. Then your Entity and some code that uses it, would be something like:
Think about that comment on Entity's declaration. If you have a long list of Behaviour's, you have to store a pointer to each one, so Entity's in your game can have any kind of Behaviour attached. Also, you can't have more than one Behaviour of each kind (which could be solved by having a data structure to hold any number of MoveBehaviours, another for InteractBehaviours, etc, but read on).
The alternative I'm going to propose aims at providing a generalization to avoid these unneeded pointers, while still maintaining the full power of behaviours. First, we change Entity to store a list (more on this later) of Behaviours.
But the problem is we now lost any record of the type of the elements on the list. How do you know which elements are MoveBehaviours, etc? In comes the Visitor design pattern, described on the great book Design Patterns, by the Gang of Four. As a quick side note, I highly recommend that book to intermediate programmers. But back to topic. A small change to Behaviour and a new BehaviourVisitor class:
The accept method is the whole idea behind Visitor. Inside Behaviour subclasses' methods, the type is know (it's a member function, after all). So each subclass, inside accept class the appropriate method on the visitor:
A BehaviourVisitor subclass can do pretty much anything inside the visit* methods. Here is a useful one:
And an use for it:
Even though the code setup is really big (considering a real engine, with several types of behaviours) I hope you can see that the usage is the same as the original example, if it used a list of each kind of Behaviour.
This is getting really long, so I think I'll split it into sessions. I don't suppose many (if any) will read this, but, if you did, I'd really appreciate if you shared your thoughts about it.
The common form of a behaviour-based engine is to have behaviour classes to represent aspects of the entities in the game, so:
Code: Select all
class Behaviour {
public:
virtual Entity * parent() = 0;
};
class MoveBehaviour : public Behaviour {
public:
virtual void update() = 0;
};
class InteractBehaviour : public Behaviour {
public:
virtual void interactWith(Entity * other) = 0;
};
// ...
Code: Select all
class Entity {
public:
virtual MoveBehaviour * moveBehaviour() = 0;
virtual InteractBehaviour * interactBehaviour() = 0;
// And a pointer to each kind of behaviour an Entity might possibly have.
};
// ...
NPC * createNPC() {
auto_ptr<Entity> npc(new NPC);
npc->addMoveBehaviour(new RandomMoveBehaviour);
npc->addInteractBehaviour(new AttackOnSightBehaviour);
return npc.release();
}
void update(Entity * e) {
if(e->moveBehaviour())
e->moveBehaviour()->update();
}
The alternative I'm going to propose aims at providing a generalization to avoid these unneeded pointers, while still maintaining the full power of behaviours. First, we change Entity to store a list (more on this later) of Behaviours.
Code: Select all
class Entity {
public:
list<Behaviour *> behaviours() = 0;
void addBehaviour(Behaviour * behaviour) = 0;
};
NPC * createNPC() {
auto_ptr<Entity> npc(new NPC);
npc->addBehaviour(new RandomMoveBehaviour);
return npc.release();
}
Code: Select all
class Behaviour {
public:
virtual Entity * parent() = 0;
virtual void accept(BehaviourVisitor * visitor) = 0;
};
class BehaviourVisitor {
public:
visitMoveBehaviour(MoveBehaviour *) {}
visitInteractBehaviour(InteractBehaviour *) {}
// One visit* method for each of Behaviour subclasses.
};
Code: Select all
void MoveBehaviour::accept(BehaviourVisitor * visitor) {
visitor->visitMoveBehaviour(this);
}
// ...
void InteractBehaviour::accept(BehaviourVisitor * visitor) {
visitor->visitInteractbehaviour(this);
}
Code: Select all
class BehaviourFilter : public BehaviourVisitor {
public:
enum class Type {
MOVE, INTERACT
};
BehaviourFilter(Type type) : m_type(type) {}
list<Behaviour *> filter(list<Behaviour *> behaviours);
void acceptMoveBehaviour(MoveBehaviour * moveBehaviour);
void acceptInteractBehaviour(InteractBehaviour * interactBehaviour);
private:
Type m_type;
list<Behaviour *> m_result;
};
list<Behaviour *> BehaviourFilter::filter(list<Behaviour *> behaviours) {
result.clear();
for(Behaviour * b : behaviours)
b->accept(this);
return result;
}
void BehaviourFilter::visitMoveBehaviour(MoveBehaviour * moveBehaviour) {
if(m_type == Type::MOVE)
result.push_back(moveBehaviour);
}
void BehaviourFilter::visitInteractBehaviour(InteractBehaviour * interactBehaviour) {
if(m_type == Type::INTERACT)
result.push_back(interactBehaviour);
}
Code: Select all
void updateMoves(list<Entity *> entities) {
BehaviourFilter filter(BehaviourFilter::Type::MOVE);
for(Entity * e : entities) {
for(Behaviour * b : filter.filter(e->behaviours())) {
MoveBehaviour * moveBehaviour = dynamic_cast<MoveBehaviour *>(b);
moveBehaviour->update();
}
}
}
This is getting really long, so I think I'll split it into sessions. I don't suppose many (if any) will read this, but, if you did, I'd really appreciate if you shared your thoughts about it.