Jump to content
  • Advertisement
Sign in to follow this  
leiavoia

How To Handle Many-To-Many Relationships?

This topic is 5400 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Are there design patterns to avoid this sort of scenario? Polymorphism isn't cutting it this time. Please let me illustrate: I'm putting the finishing touches on my collision system. It works good but i'm having a hard time deciding who does what when it's time to react to collisions. Consider for example weapons vs enemies. Let's say i have a dozen kinds of weapons and i might want to add more later if i'm feeling in the mood for a heat seeking missile. I also have several dozen types of enemies. For the basic laser-hits-badguy scenario it's very simple. The laser does damage and disappears. But what about: - enemies (or objects) that absorb certain weapons?
   // Ha! doesn't work. I'm supposed to eat fire! 
   enemy->HurtMe( int_damage_from_flamethrower )
- enemies that reflect certain weapons?
   // Doesn't work either. Object is supposed to be invincible to laser beams and reflect the laser 
   enemy->HurtMe( int_damage_from_beam_weapon );
   // how is Laser supposed to now if it should "reflect" or not?
   laser->ChangeTrajectory( backwards )
- enemies that have a resistance or invincibility to certain weapons?
   // pea shooters only do half damage because object has pea-shooter armor
   enemy->HurtMe( int_damage_from_pea_shooter )
- weapons that explode differently depending on what they hit (lasers sizzle when hitting water, pop when hitting rocks, !boink! when they hit shiny metal, etc).
   // Who knows what i hit?
   weapon->DoSpecialEffectOn( object_that_got_hit )
I hope that makes sense. It seems i can either make all the enemy types known to weapons or all the weapon types known to the enemies. And if i ever want to add a new weapon/enemy there is a lot of work across the board. How can i avoid that? I want an expandable system and simple polymorphism won't work too well. Thanks for your advice. I do appreciate it.

Share this post


Link to post
Share on other sites
Advertisement
You can do a damage bit to specify where the damage comes from. The enemy then could manipulate and apply the damage.

Share this post


Link to post
Share on other sites
You need a supervisor entity. All the game's "reactive" elements are handed to the supervisor for resolution of their physical interactions. The supervisor can aid efficiency by eliminating redundant interaction tests (collision tests between objects not in the same space; checking if A interacts with B when B has already been determined not to interact with A). This supervisor entity will be able to retrieve the properties of all reactive elements and determine the appropriate state resolutions.

Edit: I tried finding resources on a Supervisor pattern but I came up empty. Hopefully someone better versed in patterns will swing by and supply alternate nomenclature or a reference.

Share this post


Link to post
Share on other sites
Quote:
You can do a damage bit to specify where the damage comes from. The enemy then could manipulate and apply the damage.


I thought of that, but i'm talking about the problem in a more general sense. There must be a better way than giving everything a type field, whether it is GetType(), RTTI, a bitflag, or some other workaround.

The above problem was just an example, but my collision system is broken up into the following catagories for collision sorting:

Player
PlayerWeapon
Enemy
EnemyWeapon
World
WorldWeapon
Unclaimed

What i don't want is for each catagory to have to know and understand each other catagory's sub-catagories and inherited types. Ideally, i'd like this:

objectA->ReactToCollisionWith( objectB );

where objectB is one of the above types. Then they can react is a sort-of specialized way, but not ultra specialized like the difference between a "MegaBlaster 2000" and a "Graviton Beam mkII".

But it's not always so simple.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
You need a supervisor entity. All the game's "reactive" elements are handed to the supervisor for resolution of their physical interactions. The supervisor can aid efficiency by eliminating redundant interaction tests (collision tests between objects not in the same space; checking if A interacts with B when B has already been determined not to interact with A). This supervisor entity will be able to retrieve the properties of all reactive elements and determine the appropriate state resolutions.


My current system has a bool 2D table which determines which basic types can react with which other basic types. When i say basic, i really do mean it (see above post). It's the shades of detail that mess me up. Everytime something doesn't comform to the model, i'd have to add it in as a new type and program everybody else how to react to it.

The original plan was to have specific objects inherit off the basic type objects

CollisionObjectEnemy
|
\/
InheritedEnemyWithSpecializedReactionBehavior

But the specialized objects still don't know what they are dealing with when they get to this function:

void CollideWith( CollisionObjectPlayerWeapon obj ) {
// yeah, but what KIND of player weapon?
}

Share this post


Link to post
Share on other sites
Quote:
The original plan was to have specific objects inherit off the basic type objects

CollisionObjectEnemy
|
\/
InheritedEnemyWithSpecializedReactionBehavior

But the specialized objects still don't know what they are dealing with when they get to this function:


void CollideWith( CollisionObjectPlayerWeapon obj ) {
// yeah, but what KIND of player weapon?
}


make variable inside CollisionObjectPlayerWeapon that tells you what you're dealing with

Share this post


Link to post
Share on other sites
Quote:
Original post by leiavoia
My current system has a bool 2D table which determines which basic types can react with which other basic types. When i say basic, i really do mean it (see above post). It's the shades of detail that mess me up. Everytime something doesn't comform to the model, i'd have to add it in as a new type and program everybody else how to react to it.


Make it a table of pointers to function (enemy, weapon) -> result. Look up the function pointer by enemy and weapon, and call it with the enemy and weapon as parameters. Of course, this still has the downside of externalizing knowledge about how the weapons/enemies behave, but eh...

Or, to go with the

Quote:

void CollideWith( CollisionObjectPlayerWeapon obj ) {
// yeah, but what KIND of player weapon?
}


approach, look up Double Dispatch.

You could probably hybridize the two approaches in various ways, too (think functors).

Share this post


Link to post
Share on other sites
The natural way to deal with this issue is through the use of multimethods. Unfortunately, language support is somewhat lacking. Double dispatch is something of a hack for languages such as C++ which lack multimethod support. Multimethods are covered by Alexandrescu's Modern C++ Design and provided by Loki to some extent, or by Cmm. Whether you want to deal with this is another question. They are definitely the Right Solution from a theoretical perspective, but shoehorning in the support may be more of a hack than the alternative hacks. Hope this helps some.

Share this post


Link to post
Share on other sites
I've also been researching how to handle such problems in my games. All of my game interative types are derived from an Entity base and as a result, I can have several layers of inheritance. So "MegaCruiser" is a Ship derived from an entity and "MiniLaser" is a Weapon, again derived from entity.

So how am I tackling these problems? As I saw there are several solutions. One is to use the double dispatch method, the upside of this is that your compiler decides for you about which call to make - you craft the pairs and the compiler will resolve it automatically. However, this is pretty much fraught with danger if you need to add more entities into the mix later on in your game. For one, the code often becomes unmanageable, with you having to create collision/interaction pairs for every entity type there is. Far from ideal.

Another method to use is that of messaging. You will detect a collision between entities and then send a series of notification messages to cause the events. In the case of a missile hitting a ship you'd have:


Entity *src, *dest;
// A collision has occurred between src and dest
// src is a MiniCannon, dest is a MegaCrusier
src->HandleMessage( MSG_COLLIDED, (int)dest, 0 );
dest->HandleMessage( MSG_COLLIDED, (int)src, 0 );

//
void MiniCannon::HandleMessage( MSG id, int paramA, int paramB )
{
switch(id)
{
// handle messages
case MSG_COLLIDED:
{
Entity *dest = (Entity *)paramA;
// Send a messgae to instruct the dest entity to deduct 20 life points
dest->HandleMessage( MSG_DAMAGE, 20, 0);
// Release me after finished
EntityManager::DelayedRelease( this );
}

// etc, more messgaes
}
}

void MegaCruiser::SendMessage( MSG id, int paramA, int paramB )
{
switch(id)
{
// handle messages
case MSG_DAMAGED:
{
this->AdjustHealth( -paramA );
if ( this->Health() < 0 )
// die
}
}

}




This method is nicer as it means you don't HAVE to know what types you're dealing with. Another bonus is that the handler can ignore messages, so you can add more messages later and even derive more classes from here and ensure that the old messages are handled correctly. However, this method also has it's drawbacks, huge unweidly switch statements can quickly become unmanageable and making global changes to how messages are handled can be difficult.

What I'm working on now is a system that is based on the message system, but uses function pointers as delegates to deal with messages. This way you will remove the switch statements completely as each message is handled in it's own function pointer. Another benefit here is that you can reuse message handling code between different entity classes. A change made to the message function will replicate across the entire system. Message handlers are registered with the entity which contains a lookup table for the message ids. If it's not handled explicitly, the default handler will be used (essentially a functor that does nothing). I also like the idea that you are now free to swap and change message handlers at runtime, which allows for interesting things to be done within the game.

Hope this was useful to you.

Edit: Fixed spelling, etc

[Edited by - evolutional on October 6, 2004 9:53:14 AM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!