Game Development Community

dev|Pro Game Development Curriculum

Avoid Player collision with mounted objects

by Guimo · 10/08/2007 (10:24 am) · 3 comments

Precedent
While programming my game, I was forced to mount a player over other player (a player on a horse) and transfer control to the horse. Unfortunately the movement of the horse became jerky as it considered the player like an invisible wall. Disable the mounted player collisions was out of question as I still wanted it to react to arrows or other events. I searched the forums but found many people with similar problems (like mounting an object as a sword in order to keep collisions). So I decided to tackle this problem.

Requirements
This code was tested in TGEA 1.0.3. There is nothing to prevent this to work with other versions of TGEA or TGE.

The problem
When a player moves, the engine computes a collision set with surrounding objects. If a collision happens then movement is affected (stopped or deviated). When an object with a bounding box is mounted on a player, the engine considers it in the surrounding objects and collides with it causing the player to stop movement. Even disabling collisions of the mounted object wont solve the problem.

Analisis
After searching the engine for a cause, I came to the conclussion that all the collision lists are created in the Convex::updateWorkingList method. This method clears all bounding boxes not currently in contact with the player (maybe they got out of range since the last tick) and adds any other box that is now at close range. If I want to force an exclusion this is the place.

Modifications
In order to modify the behaviour of this function, start by opening the covex.h file and locate and change this:
- void updateWorkingList(const Box3F& box, const U32 colMask);
+ void updateWorkingList(const Box3F& box, const U32 colMask, SimObjectId exclude1=0, SimObjectId exclude2=0);

So, we are now adding two parameters with default ids of 0 to the method. This way the function will still work unchanged across the engine. Now open the convex.cpp and locate the updateWorkingList and change it to:

...
- void Convex::updateWorkingList(const Box3F& box, const U32 colMask);
+ void Convex::updateWorkingList(const Box3F& box, const U32 colMask, SimObjectId exclude1, SimObjectId exclude2);
...

Finally, add this to the end of the updateWorkingList method:
for (CollisionWorkingList* itr = mWorking.wLink.mNext; itr != &mWorking; itr = itr->wLink.mNext) {
      itr->mConvex->mTag = sTag;
      if (itr->mConvex->getObject()->getId() == exclude1 || itr->mConvex->getObject()->getId() == exclude2) {
         CollisionWorkingList* cl = itr;
         itr = itr->wLink.mPrev;
         cl->free();
      }
   }

This code will remove from the list any object which Id is excluded by the exclude1 or exclude2 parameters.

So we have a method that plays nice with the rest of the engine, so lets use it so we can exclude the mounting objects. Go to the player.cpp and locate the Player::updateWorkingCollisionSet method. Scroll to the end where you will find this:

if (updateSet == true) {
      const Point3F  twolPoint( 2.0f * l, 2.0f * l, 2.0f * l );
      mWorkingQueryBox = convexBox;
      mWorkingQueryBox.min -= twolPoint;
      mWorkingQueryBox.max += twolPoint;

      disableCollision();
      mConvex.updateWorkingList(mWorkingQueryBox,
         isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask);
      enableCollision();
   }

Replace it with this one:

if (updateSet == true) {
      const Point3F  twolPoint( 2.0f * l, 2.0f * l, 2.0f * l );
      mWorkingQueryBox = convexBox;
      mWorkingQueryBox.min -= twolPoint;
      mWorkingQueryBox.max += twolPoint;

      disableCollision();
	
	  ShapeBase* mountedObject = getMountedObject(0);
	  SimObjectId mountedId = mountedObject?mountedObject->getId():0;

      ShapeBase* mountingObject = mMount.object;
      SimObjectId mountingId = mountingObject?mountingObject->getId():0;

      mConvex.updateWorkingList(mWorkingQueryBox,
         isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask,mountedId,mountingId);
      enableCollision();
   }

So we are just telling the engine to locate the any object (not image) mounted in mount0 and also any object this player may be mounted in. These variables, mauntedId and mountingId are passed to the mConvex.updateWorkingList method so both will be excluded of the collision list.

Thats all, recompile and go.

How to use this
I wont go in much detail because the usage depends on your game, but try this:

function createHorse(%block, %startpos)
{
   %horse = new Player(){
      dataBlock = %block;  //a datablock depending on the horse type we want
      position = %startpos;
      rotation = "0 0 1 7.71891"; //should be random
      scale = "1 1 1";
      disableMove = "0";
      mountable = true;
      mounted = false;
   };
   MissionCleanup.Add(%horse);
   return(%horse);
}

function createKnight(%block, %startpos)
{
   %knight = new Player(){
      dataBlock = %block;  //a datablock depending on the rider type we want
      position = %startpos;
      rotation = "0 0 1 7.71891"; //should be random
      scale = "1 1 1";
      disableMove = "0";
      mountable = true;
      mounted = false;
   };
   MissionCleanup.Add(%knight);
   return(%knight);
}

...
%horse = createHorse(HeavyWarPonyData, "0 0 0");
%knight = createKnight(PaladinData, "0 10 0");
%horse.mountObject(%knight, 0);
%client.setControlObject(%horse);
...

There you go... a powerful paladin in his HeavyWarPony :)

Usage
You can use this in order to mount a player in any riding player (a wolf, a horse, a mecha).

Limitations
Currently, the player must be mounted in the mount0 only. You may modify this in the code. Remember that mounting an object is more heavy in the network than mounting an image so use it with discression in a multiplayer environment.
This code works with players only.

Improvements
Instead of passing two parameters, an array may be passed with a list of excluding objects.

Final
I really hope this is useful for someone.

#1
10/08/2007 (6:02 pm)
nice.
#2
10/09/2007 (3:35 am)
just as i need, thank you verrry much :) - i'll try this tonight
#3
12/13/2007 (3:39 am)
Very good, i just ran into the same problem :)

I would suggest to add

if (exclude1 || exclude2) {
 ....
}

arround the updateWorkingList so it does not allways loop to ignore the collisions.