Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » RTTI in Games

This thread is locked; no one can reply to it. rss feed Print
RTTI in Games
gillius
Member #119
April 2000

It is so rare that it is I making posts ;). But I've been wondering about this for a long time, and I've never quite found the kind of solution I am looking for. Note the code below:

1#ifndef _ENTITY_H_DFADF
2#define _ENTITY_H_DFADF
3 
4#include "Rect.hpp"
5class Point;
6struct BITMAP;
7 
8/**
9 * An Entity is any object that is significant to gameplay and takes up space.
10 * The main attributes about entities is that they have their own existance
11 * (because they are updated), and they collide with other entities.
12 */
13class Entity {
14public:
15 Entity();
16 
17 Entity( const Rect& newRect );
18 
19 /**
20 * Give a Point at the center of the new Entity, and the BITMAP that will
21 * represent an Entity, initializes a new Entity object.
22 */
23 Entity( const Point& loc, const BITMAP* bmp );
24 
25 virtual ~Entity();
26 
27 /**
28 * Updates the logic for this Entity object.
29 * @return true when the object has died, and wants to be removed from the
30 * game.
31 */
32 virtual bool update() = 0;
33 
34 /**
35 * Draws the Entity.
36 */
37 virtual void draw() = 0;
38 
39 /**
40 * Returns true if the given Entity collides with this one.
41 */
42 bool isCollision( const Entity& other ) const;
43 
44 /**
45 * Event that occurs when 2 entities collide. On each entity, doCollision
46 * will be called, similar to this:
47 * <code>
48 * a.doCollision( b );
49 * b.doCollision( a );
50 * </code>
51 */
52 virtual void doCollision( Entity& other );
53 
54protected:
55 Rect r;
56};
57 
58#endif

Now the game engine can treat all game objects on this level, and it does all collision detection on this level. The problem is the actions that an object must take.

Let's consider a Bullet hitting a Tank. The Bullet wants to dissapear from the game and die (in my game engines, this means it will set a flag so that it returns true from its next update call), and the Tank wants to get hurt.

This can be done from either side, but the question is where do I put and how do I call Tank's specific code for getting hurt or Bullet's specific code for dying, since the Tank might do different things depending on which way it hits.

There's several methods of doing this. What I've done so far is be lucky enough to have only several categories of objects that matter, like bullets, tanks, and unhurtable objects (terrain things), which makes a "getType" method easy to use an RTTI type solution.

With this getType method I can then use conditionals in the doCollision method. There are several techniques. I could have the Bullet detect the type of thing it hits, and if it is a Tank, cast the Entity& to a Tank& can call "damageTank( myDamage )" from the Bullet. You likewise do it from the Tank side and detect that the colliding Entity is a Bullet, cast it, and do a "life -= bullet.getDamage();"

The question I have is there a better method of doing this than using RTTI? How do you handle this in your games?

Gillius
Gillius's Programming -- http://www.gillius.org/

23yrold3yrold
Member #1,134
March 2001
avatar

You're doing it much how I do in my platformer (right down to returning true from Update() :o). I just let each object know it hit another object; eg. if the player gets hit by a goblin, the player recoils and the goblin laughs. If the goblin gets hit by the player the goblin recoils and the player gets to link another punch for a combo. So my CObject class has HitBy(CObject*) and HasHìt(CObject*) functions. See my STL shooter in my third STL article (see "My Site" in sig for link) for a simple implementation, but without HasHìt() and HitBy().

I'm also interested in other people's opinions here ....

EDIT: The function is called HasHìt(), not HasHit()!!!!!! >:(

--
Software Development == Church Development
Step 1. Build it.
Step 2. Pray.

gillius
Member #119
April 2000

Heh that's cool. I've been using a very similar system with Entity in all of my games since I started with Allegro 3 in 1998. I have had other methods from time to time like load and save for save game files, and input to do input. I don't have a virtual input method anymore because I've found better ways to do input, by using Controller objects. The problem with an input method is that the logic to do input is in the specific class. With a Controller object, it changes the structure so I can control any object (not just players) much more easily, so I can have a KeyboardController and a JoystickController and an AIController and a NetworkController etc...

But I guess the RTTI-type method is the best way to do it. Not much else one can do since somewhere logically you must determine what you hit. I need to start experimenting with dynamic_cast and such. I haven't learned the real RTTI well enough until after I started my last games.

23, can you answer my question about the subclassing with C++ RTTI? Typically what I have is like a Tank class and then a subclass of Tank for each specific tank usually with the only difference of the draw method and maybe initialization for different stats of tanks, but I want to treat the objects as Tank objects and not HighTechHumanTank or whatever.

Gillius
Gillius's Programming -- http://www.gillius.org/

23yrold3yrold
Member #1,134
March 2001
avatar

Well, here's my current hierarchy:

CGameBase
|-CEntity
| |-CPlayer
| |-CEnemy
|   |-CGoblin
|   |-CVolcano
|   |-CLuaEnemy
|-CPlatform
  |-CTilemap
  |-CSpritePlatform

CGameBase is the base class so I can shove everything into an std::list (like in the STL Shooter). It's an ABC; Update() and Draw() are declared but not defined, plus a couple of helpers for keeping bounding boxes updated. It also has a variable for it's major type (eg. ENEMY) and minor type (eg. GOBLIN). I think keeping the class type like that is faster than using dynamic_cast() (which I believe is dog slow) :)

I think a seperate class is only a good idea if there's a substancial change in code. Eg. I have goblins that follow the slopes, and some that simply run straight left and right without regard to the floor (only good on flat surfaces, but the code is faster). I use the same class for that, and just make sure CEnemy.flags & FOLLOWSLOPES is set or not. I also have some goblins that can breathe fire or ice, but I'm not sure I need to derive for that. 80% of the behavior is the same; I just have to check the type of the goblin in the Update function (via switch statement) and decide behavior. All the collision checks and drawing and getting hit is the same. I suppose there'd be no real harm to deriving for a new Goblin though; just give it a new Update function and inherit the rest. I don't thik there's a speed issue there, is there? Just changing one virtual function, which itself is only a function pointer. I'm still trying this myself, so let's hear some other people :)

Does that answer the question?

--
Software Development == Church Development
Step 1. Build it.
Step 2. Pray.

gillius
Member #119
April 2000

It didn't answer my question but the friendly people in #C++ on EFNet were helpful. My question was (given your hierarchy) was if you had a CGameBase* that pointed to a CGoblin, if I could try to cast it to a CEnemy* and treat it like a CEnemy w/o knowing that it's really a subclass of CEnemy.

Here is the class heirarchy I use though that I might create for a game with sailing ships:

Entity
|-MovingEntity
| |-Ship
|   |-SpecificShip
|-StillEntity
  |-BigRock

The Entity/MovingEntity/StillEntity are the base I use. An Entity has a rectangle, and a MovingEntity adds to that a vector. The MovingEntity's update will move the rectangle throughout the game world, hence my updates are chained -- SpecificShip::update will call Ship's update which calls MovingEntity's update.

edit: ya I forgot to tell you what the answer was. If you use dynamic_cast it works if the thing you are casting to is that thing or a subclass:

dynamic_cast<CEnemy*>( obj ) works for CEnemy or CGoblin or the others.

If you use typeid, then it's only an exact match:

if ( typeid( obj ) == typeid( CEnemy ) ) works only if obj is a CEnemy and not a CGoblin.

Gillius
Gillius's Programming -- http://www.gillius.org/

vpenquerch
Member #233
April 2000

Lookup double dispatching in "More effective C++" by Scott Meyers. Basically, you call a virtual on an object, giving a pointer/ref to the other object, and then this oibject calls back another method on the original object, with the twist that since this was done in a virtual, the actual type of the object is known, thus the second method is overloaded with all the types you need.
Though it can look a bit naff if you have many types.
Of course, the best way is to try and define several virtuals which will match what you want your objects to do, which is not always easy.

Go to: