Game Development Community

dev|Pro Game Development Curriculum

Adding new positions and moves; ie swim, crouch, crawl, prone

by Erik Madison · 06/23/2003 (1:33 pm) · 166 comments

This is loosely based on the only 2 forum threads I found dealing with swimming. Neither of them worked well enough for my needs, so I began this as a dirty rewrite. I also solved quite a few of my problems by reading and rereading Daniel Nielsens tutorials and resources.
I'm not real adept at showing anyone how or why I do something, so I apologize if this resource isn't as clear as it could be. I will try and work out any problems you may have.
Only 2 files are changed for the meat of the system, along with 2 scripts for a keybinding and callback access.

FILE: Which file to edit
[i]Which function to work in[/i]
surrounding original code
[b]New code[/b]
surrounding original code


FILE: Player.cc

[i]PlayerData::ActionAnimationDef PlayerData::ActionAnimationList[NumTableActionAnims] = 
{
[/i]
   { "back", { 0,-1,0 } },       // BackBackwardAnim
   { "side", { -1,0,0 } },       // SideLeftAnim,

   // These are set explicitly based on player actions
[b]
   // 6 new animations for the extreme basics   
   { "swimroot" },	
   { "swim" }, 
   { "crouchroot" },
   { "crouchforward" },
   { "crawlroot" },
   { "crawlforward" },[/b]
   { "fall" },       // FallAnim
   { "jump" },       // JumpAnim


[i]PlayerData::PlayerData()
{   
[/i]   
   minJumpSpeed = 500;
   maxJumpSpeed = 2 * minJumpSpeed;
[b]
   swimForce = 75;       
   swimEnergyDrain = 0;
   minSwimEnergy = 0;
[/b]
   horizMaxSpeed = 80;
   horizResistSpeed = 38; 

  
[i]   
bool PlayerData::preload(bool server, char errorBuffer[256])
{[/i]
   if (minJumpEnergy < jumpEnergyDrain)
      minJumpEnergy = jumpEnergyDrain;
[b]
   if (minSwimEnergy < swimEnergyDrain)
      minSwimEnergy = swimEnergyDrain;
[/b]
   // Validate some of the data
   if (recoverDelay > (1 << RecoverDelayBits) - 1) {

[i]   
void PlayerData::initPersistFields()
{
[/i]
   addField("jumpSurfaceAngle", TypeF32, Offset(jumpSurfaceAngle, PlayerData));
   addField("jumpDelay", TypeS32, Offset(jumpDelay, PlayerData));
[b]
   addField("swimForce", TypeF32, Offset(swimForce, PlayerData));
   addField("swimEnergyDrain", TypeF32, Offset(swimEnergyDrain, PlayerData));
   addField("minSwimEnergy", TypeF32, Offset(minSwimEnergy, PlayerData));
[/b]
   addField("boundingBox", TypePoint3F, Offset(boxSize, PlayerData));
   addField("boxHeadPercentage", TypeF32, Offset(boxHeadPercentage, PlayerData));
   
[i]   
void PlayerData::packData(BitStream* stream)
{[/i]
   stream->write(jumpSurfaceAngle);
   stream->writeInt(jumpDelay,JumpDelayBits);
[b]
   stream->write(swimForce);
   stream->write(swimEnergyDrain);
   stream->write(minSwimEnergy);
[/b]
   stream->write(horizMaxSpeed);
   stream->write(horizResistSpeed);
   
[i]   
void PlayerData::unpackData(BitStream* stream)
{[/i]
   stream->read(&jumpSurfaceAngle);
   jumpDelay = stream->readInt(JumpDelayBits);
[b]
   stream->read(&swimForce);
   stream->read(&swimEnergyDrain);
   stream->read(&minSwimEnergy);
[/b]
   stream->read(&horizMaxSpeed);
   stream->read(&horizResistSpeed);

   
[i]   
Player::Player()
{[/i]
   mFalling = false;
[b]   mSwimming = false;
   mPlayerPosition = 1; // 1=stand, 2=crouch, 3=prone
[/b]   mContactTimer = 0;


[i]
bool Player::onAdd()
{[/i]
   mState = NullState;
   setState(state);
[b]
   setPlayerPosition(1);   // 1=stand, 2=crouch, 3=prone
[/b]
   if (serverAnim.action != PlayerData::NullAnimation) {


[i]    
void Player::updateMove(const Move* move)
{[/i]
      }
      else
         mJumpSurfaceLastContact++;
[b]
   // Acceleration from Swimming
   // I don't understand physics, nor 3d math. Forgive me if this
   // looks horrid. It seems to work fairly well though, so I'll
   // be using it for now.   
	  if (!isMounted() && canSwim())    
	  {   
		  mSwimming = true;  // Not actually used, but perhaps good to have

		  VectorF pv;      
		  mEnergy -= mDataBlock->swimEnergyDrain;      
		  Point3F headRotation = getHeadRotation();      
		  pv = moveVec;		  
		  pv *= moveSpeed;      
		  F32 impulse = (5000 / mMass) * TickSec;   // times player.strength as well ?     
		  VectorF horiz = moveVec;         
		  horiz.normalize();         
		  horiz *= 0.9;
		  acc += horiz * impulse;         

		  VectorF swimAcc = pv - (mVelocity + acc);      
		  F32 swimSpeed = swimAcc.len();
		  F32 maxSwimAcc = (mDataBlock->swimForce / mMass) * TickSec;      
		  if (swimSpeed > maxSwimAcc) {        
			  swimAcc *= maxSwimAcc / swimSpeed;
			//  Con::errorf("Swimming too fast!");
		  }
		  if (swimSpeed > moveSpeed)
			  swimSpeed = moveSpeed;
		  acc += swimAcc;
		  acc.z = (-headRotation.x * (mDataBlock->swimForce / mMass) * 0.25f);

	  } else {
		  mSwimming = false;
	  }
[/b]      
   // Add in force from physical zones...
   acc += (mAppliedForce / mMass) * TickSec;

[i]         Further down[/i]

   if (!isGhost()) {
      // Vehicle Dismount
      if(move->trigger[2] && isMounted())
         Con::executef(mDataBlock,2,"doDismount",scriptThis());
    
      if(!inLiquid && mWaterCoverage != 0.0f) {
         Con::executef(mDataBlock,4,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), Con::getIntArg(mLiquidType));
[b]		 setPlayerPosition(3);[/b]
         inLiquid = true;
      }
      else if(inLiquid && mWaterCoverage <= 0.0f) {
         Con::executef(mDataBlock,3,"onLeaveLiquid",scriptThis(), Con::getIntArg(mLiquidType));
[b]		 setPlayerPosition(1);[/b]
         inLiquid = false;
      }
   } else {
      if(!inLiquid && mWaterCoverage >= 1.0f) {
[b]         setPlayerPosition(3);[/b]
         inLiquid = true;
      }   
      else if(inLiquid && mWaterCoverage < 0.5f) {
         if(getVelocity().len() >= mDataBlock->exitSplashSoundVel && !isMounted())
            alxPlay(mDataBlock->sound[PlayerData::ExitWater], &getTransform());
[b]		 setPlayerPosition(1);[/b]
         inLiquid = false;
      }
   }

                  
[i]   
bool Player::canJump()
{[/i]
	// Added position check, you cant jump while crouch, crawl
   return mState == MoveState && mDamageState == Enabled && !isMounted() && !mJumpDelay && mEnergy >= mDataBlock->minJumpEnergy && mJumpSurfaceLastContact < JumpSkipContactsMax[b] && getPlayerPosition() == 1[/b]; 
}

[b]
bool Player::canSwim()
{   
	return mState == MoveState && mDamageState == Enabled && !isMounted() && mEnergy >= mDataBlock->minSwimEnergy && mWaterCoverage >= 0.8f;
}
[/b]

[b]
void Player::setPlayerPosition(S32 position)
{
	F32 len_x, len_y, len_z;
	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;
		  }

         switch (position) {
         case 1: // Stand, walk
			 {				 
			 len_x = 1;
			 len_y = 1;
			 len_z = 2.3;
                         break;
			 }
 	     case 2:  // Crouch, sit
			 {
			 len_x = 1;
			 len_y = 1;
			 len_z = 1.1;        
			 break;
			 }
		 case 3:  // Crawl, prone
			 {	 
			 len_x = 1;
			 len_y = 2.3;
			 len_z = 0.7;
                         break;
			 }
         }
	 mObjBox.max.x = len_x * 0.5;
         mObjBox.max.y = len_y * 0.5;
         mObjBox.max.z = len_z;
         mObjBox.min.x = -mObjBox.max.x;
         mObjBox.min.y = -mObjBox.max.y;
         mObjBox.min.z = 0;
      }
      mPlayerPosition = position;
	}
	  if (isServerObject()) 
		setMaskBits(MoveMask);
}
[/b]


// This function needs a lot more work. It works well enough for
// basic testing/playing around purposes. Also, without quite a
// few more animations available, a more robust job here would be
// for naught.
[i]
void Player::pickActionAnimation()
{[/i]
   bool forward = true;
   U32 action = PlayerData::RootAnim;
   [b]
   if (getPlayerPosition() == 1) {
   ////////// enclosing original code in my own stuff
   
   ///////// End of original code
   }
   // Swim code, Position code
   else if (getPlayerPosition() == 2) {
	   action = (mVelocity.len() < 0.5) ? PlayerData::CrouchRootAnim : PlayerData::CrouchForwardAnim;
   }
   else if (getPlayerPosition() == 3) {
	   action = (mVelocity.len() < 0.5) ? PlayerData::CrawlRootAnim : PlayerData::CrawlForwardAnim;
	   if (mWaterCoverage != 0.0f) {
		   action = (mVelocity.len() < 0.5) ? PlayerData::SwimRootAnim : PlayerData::SwimAnim;
	   }
   }
   [/b]
   setActionThread(action,forward,false,false);
}
[i]
bool Player::updatePos(const F32 travelTime)
{[/i]
   setPosition(start,mRot);
   setMaskBits(MoveMask);
   updateContainer();
[b]
//  this prevents the rocket launch effect when a player in water surfaces
   if (mBuoyancy != 0 && mWaterCoverage <= 0.5f) 
   {   
      mVelocity.z += mBuoyancy * mGravity * mGravityMod * TickSec;

   }
[/b]
   if (!isGhost())  {


[i]   
U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{[/i]
[b]
	if (stream->writeFlag(mask & MoveMask)) 
		stream->writeInt(getPlayerPosition(),5);
[/b]
   if (stream->writeFlag((mask & ImpactMask) && !(mask & InitialUpdateMask)))
      stream->writeInt(mImpactSound, PlayerData::ImpactBits);
[i]        Further down......[/i]
      stream->writeFlag(mFalling);
[b]		
      stream->writeFlag(mSwimming);
[/b]
      stream->writeInt(mState,NumStateBits);


[i]
void Player::unpackUpdate(NetConnection *con, BitStream *stream)
{[/i]
[b]
	if (stream->readFlag()) {
		S32 pos = stream->readInt(5);
		setPlayerPosition(pos);
	}[/b]
   if (stream->readFlag()) 
		mImpactSound = stream->readInt(PlayerData::ImpactBits);

 [i]        Further down......[/i]
 
      mFalling = stream->readFlag();
[b]      
	  mSwimming = stream->readFlag(); // fafhrd, swim code
[/b]
      ActionState actionState = (ActionState)stream->readInt(NumStateBits);



[b][i]NEW: Updated 2/1/04 to coincide with HEAD's change to consolemethod style commands.
Change the below code (or ignore if this is your first time installing this) to what follows [/b][/i]

[i]
static S32 cgetPlayerPosition(SimObject *ptr, S32, const char **)
{
	Player* obj = static_cast<Player*>(ptr);
    return obj->getPlayerPosition();
}
static void csetPlayerPosition(SimObject *ptr, S32, const char **argv)
{
	Player* obj = static_cast<Player*>(ptr);
	obj->setPlayerPosition(dAtof(argv[2]));
}   
[/i]

void Player::consoleInit()
   Con::addCommand("Player", "getPlayerPosition", cgetPlayerPosition, "obj.getPlayerPosition()", 2, 2);
   Con::addCommand("Player", "setPlayerPosition", csetPlayerPosition, "obj.setPlayerPosition(pos)", 3, 3);


[b][i] CHANGE to this code[/b][/i]

[b]
ConsoleMethod( Player, getPlayerPosition, S32, 2, 2, "(1=stand, 2=crouch, 3=crawl)")
{
   return object->getPlayerPosition();
}

ConsoleMethod( Player, setPlayerPosition, bool, 3, 3, "(1=stand, 2=crouch, 3=crawl)")
{
   object->setPlayerPosition(dAtof(argv[2]));
}
[/b]


FILE: player.h
[i]
struct PlayerData: public ShapeBaseData {
 [/i]
    F32 jumpSurfaceAngle;      // Angle from vertical in degrees
   S32 jumpDelay;             // Delay time in ticks
 [b]  
   F32 swimForce;
   F32 swimEnergyDrain;
   F32 minSwimEnergy;
[/b]
   F32 boxHeadPercentage;
   F32 boxTorsoPercentage;
   
[i]        Further down......[/i]
   
      BackBackwardAnim,
      SideLeftAnim,

      // These are set explicitly based on player actions
[b]      SwimRootAnim,
	  SwimAnim,
	  CrouchRootAnim,
	  CrouchForwardAnim,
	  CrawlRootAnim,
	  CrawlForwardAnim,[/b]
      FallAnim,
      JumpAnim,  


[i]      
class Player: public ShapeBase
{[/i]
   ActionState mState;
   bool mFalling;                   // Falling in mid-air
[b]   bool mSwimming;
   S32 mPlayerPosition;  		    // 1=stand, 2=crouch, 3=prone   [/b]
   S32  mJumpDelay;                 // Delay till next jump

[i]        Further down......[/i]

   F32 getJumpForceModifier() { return mJumpForceModifier; }
[b]
   void setPlayerPosition(S32 pos);
   S32 getPlayerPosition() { return mPlayerPosition; }
[/b]
  protected:
   void setState(ActionState state, U32 ticks=0);
   void updateState();
   
[i]        Further down......[/i]

   bool  canJump();
[b]   bool canSwim();[/b]

   bool  haveContact()     {return !mContactTimer;}


FILE: default.bind.cs
[b]
moveMap.bind( keyboard, c, changePlayerPosition );
function changePlayerPosition(%val)
{
   if (%val)
      // calling it with a 0 will bump us up to the next position
      CommandtoServer('SetPlayerPos', 0);
}
[/b]

FILE: commands.cs
[b]
function serverCmdSetPlayerPos(%client,%pos)
{
   if (isObject(%client.player))
      %client.player.setPlayerPosition(%pos);
}
function serverCmdGetPlayerPos(%client)
{
   if (isObject(%client.player))
      return %client.player.getPosition();
   else
      return 0;
}

[/b]
#141
04/15/2008 (2:52 am)
@Adam:

Sounds like your not moving the bounding box in the anim. So your player may be moving but the bounding box is staying fixed.
#142
04/15/2008 (8:23 am)
Actually, the bounds box is in the right spot. I have tried everything with no luck.
#143
06/08/2008 (8:34 pm)
It seems that when I cycle through the animations, the player changes positions around the Bip01 node. That node stays in the same spot every time, and the player doesnt adjust its height no matter what I do with the bounds box. Any ideas here?
#144
06/08/2008 (11:43 pm)
I managed to get this to update the camera when in crouch or prone but the weapon still fires as if the player is still standing. Any ideas?
#145
06/10/2008 (7:50 pm)
***Removed, was my fault***
#146
07/10/2008 (3:21 pm)
Okay, I noticed that if you call setActionThread on a player from the console, the camera follows. But if you use the same crouch animation and use this resource for crouch, the camera won't follow. Odd....Ron, how did you get the camera to update?
#147
07/10/2008 (4:13 pm)
Well I have to be honest, I am not totally sure anymore. I was tracking down a multiplayer bug that I thought was due to this stuff and ended up removing a bunch of stuff I thought fixed my problems but in the end had no real effect.

I know I used this.

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

and this

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

and finally this

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

Be forewarned about that last one though, even though the bounding box fix mostly works, it will leave you with the problems I have here:

www.garagegames.com/mg/forums/result.thread.php?qt=76813

Basically somewhere in one of those, I fixed it, no clue where any more though.
#148
07/11/2008 (7:03 am)
I got it!

FOR ANYONE WHO WANTS THEIR FIRST PERSON CAMERA TO FOLLOW WHEN THE PLAYER ISN'T STANDING

Where it says this in Player::getEyeTransform() in player.cc:
const Point3F& scale = getScale();
   dp[3] = sp[3] * scale.x; dp[7] = sp[7] * scale.y; dp[11] = sp[11] * scale.z;
   mat->mul(getTransform(),pmat);
Change it to this:
if (getPlayerPosition() == 1)
   {
     dp[3] = sp[3] * 1; dp[7] = sp[7] * 1; dp[11] = sp[11] * 1;
     mat->mul(getTransform(),pmat);
   }
   if (getPlayerPosition() == 2)
   {
     dp[3] = sp[3] * 0.5; dp[7] = sp[7] * 0.5; dp[11] = sp[11] * 0.5;
     mat->mul(getTransform(),pmat);
   }
   if (getPlayerPosition() == 3)
   {
     dp[3] = sp[3] * 0.25; dp[7] = sp[7] * 0.25; dp[11] = sp[11] * 0.25;
     mat->mul(getTransform(),pmat);
   }

Do the same thing for where that code appears in the next function, Player::getRenderEyeTransform(). Change the 0.5s and 0.25s to however you want the camera to go down (0.5 is halfway up the player, 0.25 is a quarter way up, 1 is full, you get the idea).
#149
07/18/2008 (10:22 am)
Thanks bryce, this looks nice but for me the players hands are jittering in 1st person view.
#150
07/18/2008 (2:39 pm)
Well I found how I did it, you can try it out.

In void Player::pickActionAnimation()

Right after
U32 action = PlayerData::RootAnim;

add
bool setFSP = false;

Then right after
else if (getPlayerPosition() == 2)

add
setFSP = true;

and right after
else if (getPlayerPosition() == 3)

add
setFSP = true;

Then finally at the bottom of the function where it says
setActionThread(action,forward,false,false);

change it to
setActionThread(action,forward,false,false,setFSP);

That should do it for you.

One more thing. If you want to soup this up a bit more have a look here.
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=15078
#151
07/19/2008 (2:18 am)
Very nice, thanks a lot Ron!
#152
07/19/2008 (10:25 am)
No problem, always happy to help.
#153
07/27/2008 (8:05 pm)
My crossbow disappears when I go into the water, but when looking downward, at some angles it reappears. When I add in swim animations will I still have this problem?
#154
07/28/2008 (9:37 am)
Your bounding boxes are not setting properly. Try my resource I linked to above.
#155
07/29/2008 (4:51 pm)
Thanks a bunch Ron, that fixed the problem.
#156
11/11/2008 (4:03 pm)
ive got this so it compiles correctly, but when i try to use it in game, i get an unknown command setPlayerPostion error. i have modified the player.cc and player.h files that are in the engine folder, i changed the commands.cs that is in the example/common/server folder, and changed the default.binds.cs that is in the example/tutorial.base/client folder. when i run the torque .exe, it runs the tutorial.base folder.

i have double and triple checked what is here, and i cannot see anywhere that my version is any different. i am running 1.5.2 of the engine. im a student trying to figure this out, so im not all that experienced, but anyone have any ideas on how to fix this?
#157
11/11/2008 (4:06 pm)
You have to add the following code to the bottom of player.cc:

ConsoleMethod( Player, getPlayerPosition, S32, 2, 2, "(1=stand, 2=crouch, 3=crawl)")
{
   return object->getPlayerPosition();
}

ConsoleMethod( Player, setPlayerPosition, bool, 3, 3, "(1=stand, 2=crouch, 3=crawl)")
{
   object->setPlayerPosition(dAtof(argv[2]));
}
#158
11/11/2008 (6:20 pm)
ive got those at the very end of player.cc, except i replaced the bool with void, so it looks like:

ConsoleMethod( Player, getPlayerPosition, S32, 2, 2, "(1=stand, 2=crouch, 3=crawl)")
{
return object->getPlayerPosition();
}

ConsoleMethod( Player, setPlayerPosition, void, 3, 3, "(1=stand, 2=crouch, 3=crawl)")
{
object->setPlayerPosition(dAtof(argv[2]));
}

another thing is that when i run it from the torquedemo.exe it runs ok, but when i run the torquedemo_debug.exe, it crashes immediatley, not sure if the problems are related or not
#159
12/12/2008 (10:26 am)
hi all,

how can I slow down the movement speed for the different positions?
like, when he crawls, its WAY to fast, the same speed as running and looks un-natural

thanks
#160
03/31/2009 (7:30 pm)
If anyone is going to have the camera lower when you crouch and such, use the change suggested by Ron Nelson.

The change suggested by Bryce does not work correctly and will break vehicles. It made my buggy all jittery when ever I tried to drive it.