C++ "Is a" question

Started by
9 comments, last by Mr_Threepwood 16 years, 1 month ago
I've got a function that is called whenever two objects of type Sprite collide, sprite has several derived classes, and I'm unsure how to properly do an "is a" type of call or something on the objects. What I mean is that if the function is called and one sprite is a player bullet, and one is a monster then I want to call the TakeDamage() function on the monster. But when the method is called all I get is two sprite pointers so I need to do the check. Currently I've got it working by just checking the sprites bitmap and seeing if it is a monster, but to me that seems like a poor solution. Is there some way I can either check the class type of an object? Another solution I was thinking would be to have some constant in the base sprite class that has to be set in all derived classes or something (a virtual constant or something, does that exist?). I was tempted to just make it a member variable of the sprite class and in the constructor of all the derived classes set it, but that seems like a poor solution to me. So how would I do this properly? (this is all C++ btw). Thanks in advance.
Advertisement
Double Dispatch.

Also, it might help to look for info on the Visitor Design Pattern. There are also some articles on it in the first link in my sig.
In C++, you can determine the dynamic type of an object with virtual functions by using the typeid() operator. Let's say you have an pointer to an Object ptr. typeid(*ptr) will give you a std::type_info object that contains its dynamic type. You can also cast the pointer with dynamic_cast to see if it's derived from a given type. Ex: Frog * frog = dynamic_cast<Frog *>(ptr) will be non null iff ptr points to a Frog or something derived from a Frog.

However, the term for what you are trying to do is "double dispatch". I suggest doing a web search for "C++ double dispatch".
There are numerous ways to do this. One way could be to give Sprites a virtual function called HitByABullet(Bullet& b); or similar. Then, when the bullet's collision-response is called, it can call HitByABullet() on the thing it collided with. Since the function is virtual, each base-class can do as it pleases; play a 'ping' sound, loose health, deflect it, whatever you want.

This assumes that Sprite already has a virtual collision-response function that is called on both objects when a collision occurs.

Edit: ...and all this is just "double-dispatch" but tailored to your example.
And of course a Bullet is not a Sprite. A sprite might represent a bullet, but...
Ok thanks for the replies, but I'm still a bit confused. So here's what I have get called when any two sprites collide (bullets are represented by sprites in my program):

BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee){	Bitmap* hitterBitmap = pSpriteHitter -> GetBitmap();	Bitmap* hitteeBitmap = pSpriteHittee -> GetBitmap();	int gunDamage = g_pMainCharacter->GetGunDamage();	if ( (hitterBitmap == g_pBulletBitmap) && (hitteeBitmap == g_pMonsterBulletBitmap) )	{		pSpriteHittee->TakeDamage(g_pMainCharacter->GetGunDamage());		pSpriteHitter->TakeDamage(g_pMainCharacter->GetGunDamage());			}	else if ( (hitterBitmap == g_pBulletBitmap) && (hitteeBitmap == g_pMonster2Bitmap) )	{				pSpriteHittee->TakeDamage(g_pMainCharacter->GetGunDamage());		pSpriteHitter->TakeDamage(g_pMainCharacter->GetGunDamage());	}		return FALSE;}


So the biggest problem is the identifying what the sprites sent in are, so that I know whether or not to call the TakeDamage() function. If two monsters collide for example, nothing should happen.

So right now I've worked up a semi ugly way were I just check the bitmap of the sprite, to see if it's a bullet or not, and then if it is I call the TakeDamage method on both objects. Another bad thing is that I've got to call this on the gun bullet too, even though really the gun bullet isn't just taking damage, it's supposed to be destroyed. This works now because I set all bullets to have 1 hp which is the minimum amount of damage that a bullet can do.

So would the best way to identify a bullet be to try to cast it to a bullet, and if that fails then it's not a bullet?
C++ is set up that only an object instance knows its dynamic type. Calling a function based on the dynamic type of a single object is easy - good old virtual functions are the answer. Calling functions based on the dynamic type of two objects is not supported directly in the language. This is essentially what you want to do, you have two objects of various types, and you want to decide which code to run based on the both types. We can simulate it in C++.

I recommend you re-read the wikipedia article linked by Gage64. It has example C++ code which illustrates dynamic dispatch - with a most appropriate example (game object collision handling). [smile]

If you have any questions on the technique described there, post them here.
Ok I did read it two times, but I think the third time round gave me a better idea. So I'd do something like this:

have a virtual function called CollideWith(Sprite* hittingSprite) in the Sprite class. The implementation in the actual sprite class is set up to do nothing.

Then in the various subclasses, I put their own implementation of CollideWith that specifies different sprite types. So like in Monster (the class that all monsters are derived from), I'd have:

void CollideWith(Bullet* bulletToCollide)
{
TakeDamage (bulletToCollide->GetDamage())
}

and in Bullet I'd need to overload the CollideWith function to do something like:

void CollideWith(Sprite* spriteHit)
{
spriteHit->CollideWith(this)

//Kills the bullet sprite
kill()

}

In order to overcome the situation described in the article where you get the base classes method called instead of the derived version. Right?
Quote:Original post by Telastyn
And of course a Bullet is not a Sprite. A sprite might represent a bullet, but...


Indeed. You should have classes for the player and monster which contain Sprite objects, and sprites that are not polymorphic: there is only one kind of sprite - it's a wrapper for a bitmap and some data about what parts of the bitmap are relevant, and its size.

When a sprite collision is detected, you tell the containing objects about it, and *they* do double dispatch work. Or rather, the containing objects initiate a collision detection, and if it happens, they send the appropriate messages. If a double dispatch is needed, they do it.

An elaborate example:

// BTW, C++ has a perfectly good boolean type with real values and type safety.// Why not use it? :)bool spritesCollide(Sprite* x, Sprite* y) {  // Just check if the rectangles intersect!}void Entity::checkCollision(Entity* e) {  if (spritesCollide(sprite, e->sprite)) {    hit(e); // virtual function    e->hit(this);  }}void Bullet::hit(Entity* e) {  e->takeDamage(size);  markSelfDestroyed();}void Monster::hit(Entity* e) {  e->hitByMonster(this);}void Player::hit(Entity* e) {  e->hitByPlayer(this);}// Default collisions do nothing.virtual void Entity::takeDamage(int x) {}virtual void Entity::hitByPlayer(Player* p) {}virtual void Entity::hitByMonster(Monster* m) {}// Bullets aren't affected by anything.// Monsters aren't affected by other monsters, but they are affected by players.void Monster::hitByPlayer(Player* p) {  p->attack(this);}void Monster::takeDamage(int x) {  // take damage}// Players aren't affected by other players, but they are affected by monsters.void Player::hitByMonster(Monster* m) {  m->attack(this);}void Player::takeDamage(int x) {  // take damage}void Player::attack(Monster* m) {  m->takeDamage(someFunkyCalculation());}void Monster::attack(Player* p) {  p->takeDamage(someOtherFunkyCalculation());}


Notice how we don't just attack() collided-with sprites directly: this (admittedly complicated) process lets us negotiate to avoid "friendly fire".
Oh ok that makes a lot more sense Zalhman. I've just recently read "Beginning Game Programming" by Michael Morrison, and in his examples he always uses Sprite as the derived class, but it does make more sense that the sprites are owned by other objects since all they are supposed to be is visual representations that can be used for collision detection. The reason he didn't was probably to not confuse beginners like me by adding a new way of arranging things towards the end of the book.

At this point I'd say I'm too far in my test game to switch everything to a more proper structure, since with my C++ skills if I re-do things it usually means redo for x amount of time, debug all the errors cause as a result for x*5 amount of time. I did redo parts of the engine he gives in the book to make the game better (his coordinate system was all in ints, and I changed everything to floats so that you could have non-int velocities), but debugging that took forever. Game dev is a hell of a lot of learning so far, and I've barely scraped the surface, but it's awesome.

Edit:

As for the bool thing, I know that normal bool's exist, but for some reason in the book type "BOOL" is always used, and I guess he did it because some of the windows functions needed to have type BOOL and not bool, and he just decided to be consistent by using BOOL.

This topic is closed to new replies.

Advertisement