Game Development Community

dev|Pro Game Development Curriculum

Pseudo AutoBounds & Player multi-position improvements

by Ronald J Nelson · 07/17/2008 (9:23 am) · 7 comments

This resource is to be used in conjunction with either Erik Madsen's "Adding new Positions.." resource found here:

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4348

or the the TorqueMotion Kit which uses a lot of Eriks code and several other resources.

First let me say thankyou to Duncan Gray and his assistance. I based some of my work off of his AutoBounds resource found here:

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=12787

But as he will also say a true autobounds system has a whole mess of problems that go with it as well. In my experience it occasionally caused collision detection failures that resulted in a wide range of undesireable behavior.

I also based some of my work off of Josh Moore's resource "Adding new positions" enhancements found here:

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=8593

Specifically I am referring to his Crouch/Prone Bounding Box Fix code which is supposed to fix the bounding box problem. Unfortunately it does not entirely. What it does do is fix the collision box issue, but if you happen to use the $GameBase::boundingBox variable to make your bounding boxes visible, you will see it has no actual effect on the bounding box and therefore if you are crouched or crawling and travel under something close to the player's head, the animations will freeze.

So what I have done is make a bounding box adjustment system that "fakes" autobounds. Additionally, I adjusted different areas so damage, hit areas, and other areas that relied upon the position of the body will reflect the updated position correctly now.

I also strongly recommend adding the Conform Player to Terrain resource found here:
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3148
If you do, I am also going to mention the improvements I added for it that were not in the resource comments.

This code should work fine in all versions of TGE or TGEA.

Known issues: This code kills the scaling feature for player characters. Since this is a value that would only be able to be set as a scene object(spawned object), it should have no effect at all on your player character at all.

Disclaimer: The code works for me. I take no responsibility for damage to your games or anything else if you choose to use it. It is being released as-is, which means I am not doing upgrades on it. If you see a bug let me know and I will update that.

OK the code.

First in player.h

Right under
Point3F boxSize;           ///< Width, depth, height

add

Point3F crouchBoxSize;           ///< Width, depth, height
   Point3F proneBoxSize;           ///< Width, depth, height

then under

S32 mImpactSound;

add

Point3F boundBoxSize;           ///< Width, depth, height


Now if you added the resource above or the kit then you will have this

S32 getPlayerPosition() { return mPlayerPosition; }

after it add

bool isPositionSafe(U32 changePos);
   void updatePlayerBounds();
[code]


Finally, delete this code
[code]
   void onScaleChanged();
   Box3F mScaledBox;


Now onto player.cc/.cpp

Find the function Player::onScaleChanged() and delete it.

Then, change every instance of mScaledBox to mObjBox.

In PlayerData::PlayerData() find
boxSize.set(1.0f, 1.0f, 2.3f );


and add right after it

crouchBoxSize.set(1.0f, 1.0f, 2.3f );
   proneBoxSize.set(1.0f, 1.0f, 2.3f );

In void PlayerData::initPersistFields() find
addField("boundingBox", TypePoint3F, Offset(boxSize, PlayerData));

and add right after it

addField("crouchBoundingBox", TypePoint3F, Offset(crouchBoxSize, PlayerData));
   addField("proneBoundingBox", TypePoint3F, Offset(proneBoxSize, PlayerData));

In void PlayerData::packData(BitStream* stream) find

stream->write(boxSize.x);
   stream->write(boxSize.y);
   stream->write(boxSize.z);

and add after it

stream->write(crouchBoxSize.x);
   stream->write(crouchBoxSize.y);
   stream->write(crouchBoxSize.z);

   stream->write(proneBoxSize.x);
   stream->write(proneBoxSize.y);
   stream->write(proneBoxSize.z);

Then in void PlayerData::unpackData(BitStream* stream) find
stream->read(&boxSize.x);
   stream->read(&boxSize.y);
   stream->read(&boxSize.z);

and add after it
stream->read(&crouchBoxSize.x);
   stream->read(&crouchBoxSize.y);
   stream->read(&crouchBoxSize.z);

   stream->read(&proneBoxSize.x);
   stream->read(&proneBoxSize.y);
   stream->read(&proneBoxSize.z);

Now in Player::Player() find
mHead = delta.head;

and add after it
boundBoxSize.set(0.0f, 0.0f, 0.0f);

At the bottom of bool Player::onNewDataBlock(GameBaseData* dptr) find
mObjBox.max.x = mDataBlock->boxSize.x * 0.5f;
   mObjBox.max.y = mDataBlock->boxSize.y * 0.5f;
   mObjBox.max.z = mDataBlock->boxSize.z;
   mObjBox.min.x = -mObjBox.max.x;
   mObjBox.min.y = -mObjBox.max.y;
   mObjBox.min.z = 0.0f;

   // Setup the box for our convex object...
   mObjBox.getCenter(&mConvex.mCenter);
   mConvex.mSize.x = mObjBox.len_x() / 2.0f;
   mConvex.mSize.y = mObjBox.len_y() / 2.0f;
   mConvex.mSize.z = mObjBox.len_z() / 2.0f;

   // Initialize our scaled attributes as well
   onScaleChanged();

   scriptOnNewDataBlock();
   return true;
}

and change it to this
boundBoxSize = mDataBlock->boxSize;

   mObjBox.max.x = boundBoxSize.x * 0.5f;
   mObjBox.max.y = boundBoxSize.y * 0.5f;
   mObjBox.max.z = boundBoxSize.z;
   mObjBox.min.x = -mObjBox.max.x;
   mObjBox.min.y = -mObjBox.max.y;
   mObjBox.min.z = 0.0f;

   // Setup the box for our convex object...
   mObjBox.getCenter(&mConvex.mCenter);
   mConvex.mSize.x = mObjBox.len_x() / 2.0f;
   mConvex.mSize.y = mObjBox.len_y() / 2.0f;
   mConvex.mSize.z = mObjBox.len_z() / 2.0f;

   scriptOnNewDataBlock();
   return true;
}

In void Player::getDamageLocation(const Point3F& in_rPos, const char *&out_rpVert, const char *&out_rpQuad) find
F32 zHeight = mDataBlock->boxSize.z;

and change it to
F32 zHeight = boundBoxSize.z;

Further down in the same function find
F32 backToFront = mDataBlock->boxSize.x;
      F32 leftToRight = mDataBlock->boxSize.y;

and change it to
F32 backToFront = boundBoxSize.x;
      F32 leftToRight = boundBoxSize.y;

Next change the function bool Player::canJump() to this
bool Player::canJump()
{
   return mState == MoveState && mDamageState == Enabled && !isMounted() && !mJumpDelay && mEnergy >= mDataBlock->minJumpEnergy && mJumpSurfaceLastContact < JumpSkipContactsMax && isPositionSafe(1);
}

Again, if you added the resource or kit above you should have this function void Player::setPlayerPosition(S32 position)

Replace all of it with the following
void Player::setPlayerPosition(S32 position)
{
   if (position != mPlayerPosition) {
      if (isProperlyAdded()) 
	  {

        // Special case, this one bumps us up to the next position
        if (position == 0) 
		{ 
           position = mPlayerPosition + 1;
           if (position > 3)
              position = 1;
        }
		if(isPositionSafe(position) == false)
		{
			return;
		}

		else
		{
			mPlayerPosition = position;
			updatePlayerBounds();
		}
	  }
   } 
}

void Player::updatePlayerBounds()
{
   F32 len_x, len_y, len_z;

   S32 position = getPlayerPosition();
   if (!isProperlyAdded())
	   return;
   // Special case, this one bumps us up to the next position
   if (position == 0) 
   { 
	   position = mPlayerPosition + 1;
	   if (position > 3)
		   position = 1;
   }
   switch (position) 
   {
	   case 1: // Stand, walk
	   {
		   boundBoxSize = mDataBlock->boxSize;
		   break;
	   }
	   case 2:  // Crouch, sit
	   {
		   boundBoxSize = mDataBlock->crouchBoxSize;
		   break;
	   }
	   case 3:  // Crawl, prone
	   {
		   boundBoxSize = mDataBlock->proneBoxSize;
		   break;
	   }
   }
   mObjBox.max.x = boundBoxSize.x * 0.5f;
   mObjBox.max.y = boundBoxSize.y * 0.5f;
   mObjBox.max.z = boundBoxSize.z;
   mObjBox.min.x = -mObjBox.max.x;
   mObjBox.min.y = -mObjBox.max.y;
   mObjBox.min.z = 0.0f;

   // Setup the box for our convex object...
   mObjBox.getCenter(&mConvex.mCenter);
   mConvex.mSize.x = mObjBox.len_x() / 2.0f;
   mConvex.mSize.y = mObjBox.len_y() / 2.0f;
   mConvex.mSize.z = mObjBox.len_z() / 2.0f;

   if (isServerObject())
	   setMaskBits(MoveMask);
} 

bool Player::isPositionSafe(U32 changePos)
{
	Point3F end;
	RayInfo above;

	//Get the center of the bounds
	Point3F center;
	center = getBoxCenter();

	if(mPlayerPosition == 3 && changePos == 1)
	{
		end.x = center.x;
		end.y = center.y;
		end.z = center.z + (mDataBlock->boxSize.z);
		if (gClientContainer.castRay(center, end, STATIC_COLLISION_MASK, &above))
		{
			return false;
		}
	}
	else if(mPlayerPosition == 3 && changePos == 2)
	{
		end.x = center.x;
		end.y = center.y;
		end.z = center.z + (mDataBlock->crouchBoxSize.z * 0.5f);
		if (gClientContainer.castRay(center, end, STATIC_COLLISION_MASK, &above))
		{
			return false;
		}
	}
	else if(mPlayerPosition == 2)
	{
		end.x = center.x;
		end.y = center.y;
		end.z = center.z + (mDataBlock->boxSize.z * 0.75f);
		if (gClientContainer.castRay(center, end, STATIC_COLLISION_MASK, &above))
		{
			return false;
		}
	}
	return true;
}

In bool Player::updatePos(const F32 travelTime) find
getTransform().getColumn(3,&delta.posVec);


and add right after it
updatePlayerBounds();

If you addded the Conform Player to terrain resource go back and remove any code you added in that resource that refers to "conformToGround" AFTER you change the following

there will be two instances of this
if (mDataBlock && mDataBlock->conformToGround)

change them both to
if(getPlayerPosition() == 3 && !mFalling)

That way it conforms the player to the terrain while crawling.

Now in void Player::setRenderPosition(const Point3F& pos, const Point3F& rot, F32 dt) find
F32   boxRad = (mDataBlock->boxSize.x * 0.5f);

and change it to
F32   boxRad = (boundBoxSize.x * 0.5f);

Finally, the player script file.

Find(your settings will probably be different from mine)
boundingBox = "1.3 1.3 2.40";

and add
crouchBoundingBox = "1.3 1.3 1.75";
   proneBoundingBox = "1.3 2.60 0.75";

Personally I recommend testing your boxes with $GameBase::boundingBox set to true. Since this only works in debug mode with stock TGE/TGEA I took commented out the debug conditions on mine so I could just turn them on when I wanted to.


This code works fine with the climbing resource, Ragdoll pack, Walking resource(included in TorqueMotion), and the Air Control resource.

Hope this works for you as well as it does for me.

#1
07/17/2008 (4:34 pm)
Very cool!
#2
08/06/2008 (7:33 am)
Awesome. This is perfect for all the complicated stuff I want to with players :P
#3
08/07/2008 (4:57 am)
Awesome work... man!
#4
09/16/2008 (8:53 am)
Looking at this again, the functionality of isPositionSafe doesn't seem great. I'm going to look at a solution that uses the bounding box itself, like checkDismountPosition, though that won't be perfect either :P.

EDIT: This is what I worked up, but it doesn't seem to work :P.
bool Player::checkStanceSafe(U32 oldStance, U32 newStance)
{
   AssertFatal(getContainer() != NULL, "Error, must have a container!");
 
   disableCollision();
 
   Box3F wBox = mObjBox;
   wBox.min += getPosition();
   wBox.max += getPosition();
 
   Point3F size = mDataBlock->boxSize[newStance];
 
   wBox.max.x = size.x * 0.5f;
   wBox.max.y = size.y * 0.5f;
   wBox.max.z = size.z;
   wBox.min.x = -wBox.max.x;
   wBox.min.y = -wBox.max.y;
   wBox.min.z = 0.0f;
 
   EarlyOutPolyList polyList;
   polyList.mNormal.set(0.0f, 0.0f, 0.0f);
   polyList.mPlaneList.clear();
   polyList.mPlaneList.setSize(6);
   polyList.mPlaneList[0].set(wBox.min,VectorF(-1.0f, 0.0f, 0.0f));
   polyList.mPlaneList[1].set(wBox.max,VectorF(0.0f, 1.0f, 0.0f));
   polyList.mPlaneList[2].set(wBox.max,VectorF(1.0f, 0.0f, 0.0f));
   polyList.mPlaneList[3].set(wBox.min,VectorF(0.0f, -1.0f, 0.0f));
   polyList.mPlaneList[4].set(wBox.min,VectorF(0.0f, 0.0f, -1.0f));
   polyList.mPlaneList[5].set(wBox.max,VectorF(0.0f, 0.0f, 1.0f));
 
   if (getContainer()->buildPolyList(wBox, sCollisionMoveMask, &polyList))
   {
      enableCollision();
      return false;
   }
 
   enableCollision();
   return true;
}
I have two parameters for future use - I just whipped this up to start with.
I do have some of my own code in there, namely the bit with the boxSize array. This should be replaced with the appropriate box sizes for whatever code you're using.
#5
09/16/2008 (10:13 am)
Thats fine Daniel. My technique is based upon using the absolute minimum number of calculations possible for position changes. If you want a more exact accuracy, then yes you would need to do some additional changes to get a scan length more based upon actual dimension differences between box sizes.
#6
10/21/2008 (10:04 am)
I actually agree with your way now. Stance changes probably don't need such a degree of fidelity, raycasts would work fine. Just got to figure out where to cast.
#7
01/15/2009 (10:19 pm)
Great resouce. While seems that it's based on several other resouces. Is there one complete kit for TGEA1.7.1 or TGEA1.8.0?

Thanks,