Handling collision is a common problem in games. The tricky part is that the way a collision is handled depends on both of the things that are colliding. You want to be able to select an appropriate method based on a pair of classes, not just one. Unfortunately, most OOP languages like C++ only support single dynamic dispatch (which is what virtual methods and overriding are).
What you want is double dispatch. You can imagine four functions like this:
void playerToPlayerCollision(Player* a, Player* b) { /* ... */ }
void playerToBulletCollision(Player* a, Bullet* b) { /* ... */ }
void bulletToPlayerCollision(Bullet* a, Player* b) { /* ... */ }
void bulletToBulletCollision(Bullet* a, Bullet* b) { /* ... */ }
And somehow the language would magically pick the right one based on the two Entity*
objects that collided. You can make that magic work using a combination of overriding (for the first dispatch) and the visitor pattern (for the second). It looks like this:
class Entity {
// Physics engine calls this:
virtual void collide(Entity* other) = 0;
virtual void collideWithPlayer(Player* player) = 0;
virtual void collideWithBullet(Bullet* bullet) = 0;
}
class Player : public Entity {
// Double dispatch:
virtual void collide(Entity* other) {
other->collideWithPlayer(this);
}
// Actual collision-handling functions:
virtual void collideWithPlayer(Player* player) {
// Player -> Player collision.
}
virtual void collideWithBullet(Bullet* bullet) {
// Player -> Bullet collision.
}
}
class Bullet : public Entity {
// Double dispatch:
virtual void collide(Entity* other) {
other->collideWithBullet(this);
}
// Actual collision-handling functions:
virtual void collideWithPlayer(Player* player) {
// Bullet -> Player collision.
}
virtual void collideWithBullet(Bullet* bullet) {
// Bullet -> Bullet collision.
}
}
When the physics engine calls collide
, that gets overridden by Player
and Bullet
. Those overrides dispatch on the first of the colliding objects. They immediately call another virtual method on the other colliding object. That method does the second dynamic dispatch and you ultimately select one of four methods based on the types of both arguments. No casting required.
This pattern only has two real limitations:
It's complex and obscure. Unless you recognize the pattern, the code here is kind of strange looking and hard to grasp.
You're constrained to a fixed set of subclasses. You'll note that Entity
now is aware of the Bullet
and Player
subclasses. It will need to be aware of all subclasses that you dispatch on, which defeats the open-ended subclasses you usually get in C++.
If you can live with those, this pattern works pretty well.