5
\$\begingroup\$

For my game,

Everything inherits from Entity, then other things like Player, PhysicsObject, etc, inherit from Entity. The physics engine sends collision callbacks which has an Entity* to the B that A collided on. Then, lets say A is a Bullet, A tries to cast the entity as a player, if it succeeds, it reduces the player's health.

Is this a good design?

The problem I have with a message system is that I'd need messages for everything, like: entity.sendMessage(SET_PLAYER_HEALTH,16);

So that's why I think casting is cleaner.

\$\endgroup\$
1
  • 7
    \$\begingroup\$ You're going to end up with a messy solution regardless of what you do simply because your overuse of inheritance. Component-based design is the way of the future. \$\endgroup\$
    – Tetrad
    Jun 18, 2011 at 23:11

6 Answers 6

9
\$\begingroup\$

Virtual function calls are your messages:

void Player::onCollision(const Entity& collisionEntity)
{
    Player::setHealth(Player::getHealth() - collisionEntity.damageValue());
}

Provide them with (empty) default implementations so an Entity will only handle the events it wants to.

When your Entity class inevitably starts to become bloated, it's either time to push some methods down the hierarchy, or begin moving to a component-based design.

\$\endgroup\$
5
  • \$\begingroup\$ What happens if the entity is not a bullet? \$\endgroup\$
    – momboco
    Jun 18, 2011 at 18:51
  • \$\begingroup\$ @momboco: Good point; perhaps the bullet should do the damaging: Bullet::onCollision(..) { collisionEntity.takeDamage(damageValue); }. However, which way you choose is moot to the overall question of how to notify entities of events. \$\endgroup\$ Jun 18, 2011 at 18:57
  • 1
    \$\begingroup\$ The problem with that the bullet decide the damage is if we want other behavior when the bullet collides. For example, we want to put a impact effect in a wall. Or to do less damage when impacts in a monster. \$\endgroup\$
    – momboco
    Jun 18, 2011 at 19:05
  • \$\begingroup\$ @momboco: These issues exist exactly the same when using a messaging system, and are solved in exactly the same way when using virtual functions. For instance, the analog of the messaging code from your answer would be void Player::bulletImpact(BulletImpact bi) { doDamage(bi); } The only difference is that the virtual methods way is cleaner (doesn't require large if-else/switch chains), faster, and the data is strongly-typed. \$\endgroup\$ Jun 18, 2011 at 19:26
  • \$\begingroup\$ Yes, it can be achieved with methods defined in player. Message systems have the advantage that bullet doesn't need to include player file headers to access his methods. The large "else if" problem in my answer is true. Sorry for my comments, I only want to be constructive. \$\endgroup\$
    – momboco
    Jun 18, 2011 at 19:37
3
\$\begingroup\$

Bullet collide with an object ( whichever ) and sends a message to the object. bullet.sendMessage( other_object, BULLET_IMPACT,16); the bullet doesn't need to know the details of the destiny object.

If the object is a player.

void Player::handleMessage( Message* msg )
{
   if ( msg->type() == BULLET_IMPACT )
       doDamage( msg->data() );
}

The destiny decides the action.

Avoid cast wherever you can.

\$\endgroup\$
2
\$\begingroup\$

Dynamic casting is really not something you want to design your code around.

Instead, virtual functions on your Entity/Player/PhysicsObject/etc classes are your best friends.

\$\endgroup\$
2
\$\begingroup\$

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:

  1. It's complex and obscure. Unless you recognize the pattern, the code here is kind of strange looking and hard to grasp.

  2. 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.

\$\endgroup\$
1
\$\begingroup\$

There's nothing wrong with a good dynamic_cast. It is not the best design in many situations, but in some, it simply is the best, and in this kind of code, dynamic_cast is the right way to go. You don't want to make your derived classes aware of each other, and you don't want to put things in the interface that don't make sense. For example, what if a player collides with a wall? Unless you think that should do damage, it doesn't make sense to give Entity a DamageValue- because not all Entities have damage or do damage to the player upon collision.

\$\endgroup\$
0
\$\begingroup\$

Aside from using virtual functions as others already mentioned, you should also store an enum in the "base" class that specifies the type. Something like:

enum eEntType{
ENT_PLAYER, 
ENT_PHYS, 
ENT_ETC
};
eEntType m_eType;

Have the base class constructor take in an eEntType which sets it at the get-go. Then make read-only accessors to them. This is a quicker way of differentiating these objects, and it's good on the go/any time.

All the deriving subclasses should use the member initializer list passing a 'hardcoded' value of its type to the base ctor.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .