|
This thread is locked; no one can reply to it. |
1
2
|
collision handling |
imaxcs
Member #4,036
November 2003
|
Hi Allegators! I have a problem on how to implement the collision handling in my game in a good style. Let's say I have a class CObject, a class CCollidingObject and furthermore the classes CShip, CPowerup, and CBullet. I think the names are pretty self-explanatory. Anyway the class hierarchy is like this: CObject Now, I have a singleton-class which stores a list to all CCollidingObjects. The collision-detection is not the problem. What I need help with is where to respond on a collision. I have thought up two different approaches: 1.) Every CCollidingObject has a virtual method called "collided_with(CCollidingObject* object)" where the collision is handled. My problem with this is, that I will probably need to make downcasts to the specific classes. For example, if the player's ship collides with a power-up, the method needs to downcast to see if the object is a CPowerup and if it is, do the correct thing (gain hull energy or health, give more ammo, ...) I really don't like this approach because of the downcasts and I will most probably run into include-loops. 2.) The collisions are handled somewhere global in one place. The downside with this is, that I will also need downcasts to call the specific methods (like decrease_health(), destroy() or the like) and I would also need to make all these methods public. I would like you to give me advice on how to resolve this. Is there a cleaner way (without downcasts at best) to do this? How would you do this? Thank you for your time!
|
Kris Asick
Member #1,424
July 2001
|
I'm not an expert in OOP... yet... but I think if you want to stay fully OOP compliant, you may be stuck with having to downcast. Though my approach with my game engine was to reduce collision detection to a process only some objects can initiate and which only the base class information is distributed in the process. The base class objects store enough basic information that knowing specifics about any particular derived class would be unnecessary. When the two objects collide, each object should primarily affect its OWN status, not each other's, beyond the basics. Thus the level within the class hierarchy that the collision takes place at should not have anything critical to the collision further derived. IE: If two objects distribute kinetic energy by mass on an impact with each other, you wouldn't store the mass of the object in a derived class above the collision object, you would store it below or in the collision object itself. So if you need to know whether something which hits a ship is a powerup or not and what kind of powerup it is, that information needs to be stored at the collision level, or closer to the base level, and then the ship, not the powerup, would process it. ...If that makes any sense at all. I'm still figuring all this OOP stuff out so I could be way off my rocker there... --- Kris Asick (Gemini) --- Kris Asick (Gemini) |
Audric
Member #907
January 2001
|
Collision is probably irrelevent for : Which leaves you CShip - everything else (including CShip) edit: after further research, in the general case what you need is multiple polymorphism, which doesn't seem to be available in C++. virtual collision(CollidingObject &a,CollidingObject &b) that you would derive for all 3x3 combinations, and at run-time it would select which one to use depending on a and b. |
Fladimir da Gorf
Member #1,565
October 2001
|
One way would be to have a map from two type IDs to a function pointer. OpenLayer has reached a random SVN version number ;) | Online manual | Installation video!| MSVC projects now possible with cmake | Now alvailable as a Dev-C++ Devpack! (Thanks to Kotori) |
imaxcs
Member #4,036
November 2003
|
Thanks for your suggestions so far but I am not satisfied. Kris Asick: If I would put all the necessary informations that I need to fully handle every collision into the CCollidingObject-class or even higher in the tree, it would defeat one of the most basic OOP-features. Why should a CCollidingObject have a member which stores the shield energy, as a ship would have? I really don't like that! Audric: well, if for example a bullet collides with a ship, who would then destroy the bullet? Should the ship destroy the bullet? IMHO, when they collide the ship AND the bullet should both handle the collision, the ship should decrease it's health/shield energy, and the bullet should set some flag, so that it gets destroyed in the next frame. (I already figured out that having objects being destroyed at a global place is by far the best method) edit: Audric: that's a feature I miss in C++ since I have started using it! Flad: care to elaborate a bit more? I am not quite sure I did understand.
|
Tobias Dammers
Member #2,604
August 2002
|
First of all, divide the collision problem into two sub-problems: collision DETECTION and collision RESPONSE. cBoundingObject (pure virtual) | +- cBoundingSphere | +- cBoundingBox | +- cBoundingPoint | +- cBoundingLine | +- cWhateverBoundingThingy Whether the actual collision check should be a member function or a global function is a matter of taste. If all your objects use the same bounding type, there is no need for an entire class hierarchy and virtual functions, but it's still a good idea to encapsulate the collision detection. When it comes to collision response, you should group all possible sorts of collision responses into groups, such as: --- EDIT --- --- |
Audric
Member #907
January 2001
|
edit: I didn't precise below, this is about collision RESPONSE Personnally, I use a very Actor-oriented model: What does a bonus do when it touches something ? What does a enemy bullet do when it touches something ? etc. The messaging is simply done by calling for example: bool actor::hit_by_missile(missile * this) |
Tobias Dammers
Member #2,604
August 2002
|
I prefer having the scene inform the objects involved that they are touching, and decide what to do with them according to their stats. --- |
ImLeftFooted
Member #3,935
October 2003
|
The common practice is for CCollidingObject to handle it on movement. If any sub-class of CCollidingObject ever wants to move it must call a movement function implemented by CCollidingObject. That function checks if the new position collides with any other CCollidingObjects. Pretty simple really. No down-up or any casting at all. |
Audric
Member #907
January 2001
|
You assume that only movement can cause a collision. Well, anyway, do what's most suited for the game at hand. A classic shoot'em up will typically have 70% objects on screen as player projectiles, so better "optimize" for this situation. |
ImLeftFooted
Member #3,935
October 2003
|
All the events that can trigger a collision must be implemented in CCollideObject. It is the OOP way. |
Kris Asick
Member #1,424
July 2001
|
Quote: If I would put all the necessary informations that I need to fully handle every collision into the CCollidingObject-class or even higher in the tree, it would defeat one of the most basic OOP-features. Why should a CCollidingObject have a member which stores the shield energy, as a ship would have? I really don't like that! Why do you need that much information to be accessible? In the case of a weapon hitting a ship, the weapon has a damage value, the ship has its shields. A collision is detected by the system and both objects run their collision routines. Ship Collision: Weapon Collision: You never have to send the damage or shield data through the collision class since the ship class has its own method of handling collisions and so does the weapon class. Does that make more sense? --- Kris Asick (Gemini) --- Kris Asick (Gemini) |
ImLeftFooted
Member #3,935
October 2003
|
class Collidable { void moveTo(int x, int y) { if(.. collision ..) this->onCollision(otherCollidablePointer); } virtual void onCollision(Collidable*) {} };
class Ship : public Collidable { ... }; class Missle : public Collidable { virtual void onCollision(Collidable *c) { Ship *s = dynamic_cast<Ship*>(c); if(s) s->inflictDamage(); dienow(); } }; This is the proper OOP style. If you're going to do differen't go for it but know that you are not doing OOP style. |
Audric
Member #907
January 2001
|
Quote: This is the proper OOP style. If you're going to do differen't go for it but know that you are not doing OOP style. no comments... |
axilmar
Member #1,204
April 2001
|
Here is a perhaps easier way to handle the problem (If only C++ had multimethods!): have a base class CCollision with a virtual method 'execute': class CCollision { public: virtual void execute() = 0; }; Then have a template subclass with two types, one for each type of object; this class acts as a container of the two types:
Then have a specific subclass which handles specific types. For example, to manage the collision between the ship and the powerup, you can do the following: class CShipPowerUpCollision : public CBinaryCollision<CShip, CPowerUp> { public: CShipPowerUpCollision(CShip *ship, CPowerUp *powerUp) : CBinaryCollision<CShip, CPowerUp>(ship, powerUp) { } virtual void execute() { CShip *ship = getFirstObject(); CPowerUp *powerUp = getSecondObject(); if (ship->collidesWith(powerUp)) { ship->power++; } } }; Then place all collision objects in a list which you frequently scan, invoking the 'execute' method: std::list<CCollision *> collisions; collisions.push_back(new CShipPowerUpCollision(myShip, myPowerUp)); while (gameLoop) { ... for(std::list<CCollision *>::iterator it = collisions.begin(); it != collisions.end(); ++it) { CCollision *collision = *it; collision->execute(); } } EDIT: If there are not many collisions to test, perhaps the procedural approach is best: while (gameLoop) { ... testCollision(myShip, powerUps); testCollision(myShip, bullets); ... }
|
imaxcs
Member #4,036
November 2003
|
Thank you again for your help! It seems there is no 100 percent clean method to do this. But you all gave quite some cool stuff to make it at least come close. I especially like axilmar's idea! Thank you! edit: ok, axilmar, I really like your approach but there is one thing I am still not fond of. std::list<CCollision *> collisions; collisions.push_back(new CShipPowerUpCollision(myShip, myPowerUp)); while (gameLoop) { ... for(std::list<CCollision *>::iterator it = collisions.begin(); it != collisions.end(); ++it) { CCollision *collision = *it; collision->execute(); } } How do I check, which collision has occured? How do I know it's a CShipPowerUpCollision and not something else?
|
Tobias Dammers
Member #2,604
August 2002
|
Quote: How do I check, which collision has occured? How do I know it's a CShipPowerUpCollision and not something else?
By checking the types of the colliding objects, duh. --- |
axilmar
Member #1,204
April 2001
|
Quote: How do I check, which collision has occured? How do I know it's a CShipPowerUpCollision and not something else? You shouldn't do that! the whole point of putting the collision code into objects is that you don't have to do that. Put all the code that must be executed in a collision inside the CCollision subclass. Here is a wikipedia article about multimethods: |
ImLeftFooted
Member #3,935
October 2003
|
You can use the "CShipPowerUpCollision" system if you want, but your project wont last more then a week when you realize how many utterly useless objects you've created and what a pain in the ass it is. Then your code will turn so ugly you'll give up on the project and start over But, some lessons have to be learned the hard way. |
piccolo
Member #3,163
January 2003
|
this works nice the script pointer hard coded id based.
one of the objects
wow |
axilmar
Member #1,204
April 2001
|
Quote: You can use the "CShipPowerUpCollision" system if you want, but your project wont last more then a week when you realize how many utterly useless objects you've created and what a pain in the ass it is. Then your code will turn so ugly you'll give up on the project and start over But, some lessons have to be learned the hard way. Hey, I couldn't agree more. That's why I wrote that if his collision detection needs are simple, the best approach is the procedural one. |
imaxcs
Member #4,036
November 2003
|
Thanks for all your thoughts! Although I hope that your assumptions concerning my project failing are wrong. I use a CCollisionHandler which's process-method currently looks like this:
In the first part, I check all objects for collisions and create CCollision-objects which are saved in a temporary list. In the second part, I handle all the collisions based on it's type. Works quite nice, so I am pleased. edit: damn, piccolo you are random!
|
ImLeftFooted
Member #3,935
October 2003
|
I suppose that could work, assuming all projectiles are implemented as 'bullets'. You've sorted created your own OOP paradigm without using C++ syntax. std::lists are quite bloated by the way, using them might hurt your fps. |
Tobias Dammers
Member #2,604
August 2002
|
Quote: std::lists are quite bloated by the way, using them might hurt your fps. How so? A list uses some extra memory for the list structure (3 pointers per node plus 2 for the head and tail nodes IIRC, so 12n + 8 bytes of overhead for n objects on a typical 32 bit system), true. But it all depends on what you are doing. Lots of random insertions / deletions? Lists are your friend. The time you save in memory reallocation due to vector resizing should easily compensate for the memory overhead, unless we're talking thousands and thousands of objects. --- |
ImLeftFooted
Member #3,935
October 2003
|
std::list goes out of the way to support a large number of features. This bloat has a high cost. If you can manage to make your list simpler and not require these advanced features you will be saving a lot of overhead. Quote: How so? A list uses some extra memory for the list structure (3 pointers per node plus 2 for the head and tail nodes IIRC, so 12n + 8 bytes of overhead for n objects on a typical 32 bit system), true. But it all depends on what you are doing. Yes there is some memory overhead and something tells me its larger then this (maybe some test-cast programs are in order?) but memory is not my main concern. iterating a list is costly, with at the very least a pointer lookup per iteration. A pointer lookup typically will cost you a whole cycle. So the overhead is in actually iterating the list, not insertion and deletion. If you must insert randomly and require deletes to happen instantly, then you will be forced to use std::list. But an experienced coder will realize that most list uses can be achieved without these two requirements. Lets take an example. Say you had a particle engine. Particle engines are always trying to cram more particles and more effects into the game without hurting the frame rate. Every little bit of optimization counts. In order to avoid having to resize your list, you can employ a simple trick of adding a flag to each particle object specifying if its alive or not. Instead of actually removing the element and reallocating the whole array, you can simply flag the element as dead and re-use it when you have to create new particles later on. This technique is used in a project I'm working on currently. I'm going to report the FPS for level 1 during normal place as well as the particle count. I'm then going to switch over to a std::list type and report the same results. (I'll put them in my next post / edit). [edit] So the difference isn't amazingly large in this case, but the more particles the more dramatic the effect. |
|
1
2
|