m4ster r0shi wrote: |
---|
another option is the visitor design pattern |
Ideka wrote: |
---|
I don't think I can apply it to this situation though |
I assume that your class hierarchy looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class Actor { /* ... */ };
class Player : public Actor { /* ... */ };
class AbstractEnemy : public Actor { /* ... */ };
class AbstractObstacle : public Actor { /* ... */ };
// no more direct Actor subclasses (this is important)
class ConcreteEnemyA : public AbstractEnemy { /* ... */ };
class ConcreteEnemyB : public AbstractEnemy { /* ... */ };
class ConcreteEnemyC : public AbstractEnemy { /* ... */ };
// more concrete enemies ...
class ConcreteObstacleA : public AbstractObstacle { /* ... */ };
class ConcreteObstacleB : public AbstractObstacle { /* ... */ };
class ConcreteObstacleC : public AbstractObstacle { /* ... */ };
// more concrete obstacles ...
|
The facts that...
(1) The direct Actor subclasses are likely to remain
unchanged throughout the development of your game.
(2) The direct Actor subclasses represent concepts that don't look like
they should have a common interface (other than basic actor stuff).
...indicate that the visitor pattern could be of help.
Suppose that you want to add a function that will toughen up all enemies on the screen by
improving their weapon(s) and / or armor. Since different concrete enemies have different kinds
of weapons and armors, it makes sense to add a virtual function in your enemy class hierarchy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
AbstractEnemy : public Actor
{ /* ... */
virtual void ImproveWeaponAndArmor() = 0;
/* ... */ };
ConcreteEnemyA : public AbstractEnemy
{ /* ... */
virtual void ImproveWeaponAndArmor() { /* ... */ }
/* ... */ };
ConcreteEnemyB : public AbstractEnemy
{ /* ... */
virtual void ImproveWeaponAndArmor() { /* ... */ }
/* ... */ };
// etc...
|
However, it doesn't make sense to add that virtual function earlier in the hierarchy
(i.e. in the Actor class), because it doesn't make sense to have such a function for a
player (as your player is a monk and doesn't use weapons or armor) or an obstacle.
Here's where the visitor pattern comes into play:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
class OperationOnActor
{
public:
virtual void OperateOnPlayer (class Player * p) {}
virtual void OperateOnEnemy (class AbstractEnemy * e) {}
virtual void OperateOnObstacle(class AbstractObstacle * o) {}
};
class Actor { /* ... */ virtual void ApplyOperation(OperationOnActor * op) = 0; /* ... */ };
class Player : public Actor
{ /* ... */
virtual void ApplyOperation(OperationOnActor * op)
{ op->OperateOnPlayer(this); }
/* ... */ };
class AbstractEnemy : public Actor
{ /* ... */
virtual void ApplyOperation(OperationOnActor * op)
{ op->OperateOnEnemy(this); }
/* ... */ };
class AbstractObstacle : public Actor
{ /* ... */
virtual void ApplyOperation(OperationOnActor * op)
{ op->OperateOnObstacle(this); }
/* ... */ };
class ImproveEnemyWeaponAndArmor : public OperationOnActor
{
public:
virtual void OperateOnEnemy(AbstractEnemy * e) { e->ImproveWeaponAndArmor(); }
};
|
Doing the dispatch this way saves you space (you don't have to store
anything about the type of your objects), keeps the interface clean and
also makes darkestfright happy, as you don't have to use dynamic_cast.
The drawback is that the operation is attempted to be
executed on every actor, and that is a waste of time.
m4ster r0shi wrote: |
---|
Is storing the active instances in separate vectors
(depending on their type) out of the question? |
Ideka wrote: |
---|
You bet it is. |
Why is that? Does that vector represent some kind of 2D map? In this
case, you could use additional containers to keep track of your objects:
1 2 3 4 5 6
|
std::vector<Actor *> game_map;
std::set<AbstractEnemy *> enemies;
std::set<AbstractObstacle *> obstacles;
Player * player;
|
Just make sure that every time you add / remove an actor
to / from your game_map you also update the respective set.
Doing it like this saves you time, because now you don't have to iterate over the whole
game_map to apply an operation on your enemies. You just use the appropriate set.
However, mind that it wastes memory.
The way you do it now neither saves time (as you check every actor of your
vector to find out if it's of the type you want) or space (because you store type
information on every object) nor makes darkestfright happy (as you still have
to use dynamic_cast after you find out that the object is of the type you want) :P