|
|
| RTTI in Games |
|
gillius
Member #119
April 2000
|
It is so rare that it is I making posts
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 |
|
23yrold3yrold
Member #1,134
March 2001
|
You're doing it much how I do in my platformer (right down to returning true from Update() I'm also interested in other people's opinions here .... EDIT: The function is called HasHìt(), not HasHit()!!!!!! -- |
|
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 |
|
23yrold3yrold
Member #1,134
March 2001
|
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? -- |
|
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 |
|
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. |
|
|