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]
#121
05/02/2007 (4:57 am)
Question to those who have this working:
What if you crawl or crouch under something and try to stand up?
#122
06/09/2007 (3:13 am)
Jari you should use a RayIntersaction for check...this is my SetPlayerPosition ( i don't use swim animation)


void Player::setPlayerPosition(S32 position)
{
F32 len_x, len_y, len_z;

S32 old_position = mPlayerPosition;

if (position != mPlayerPosition)
{
if (isProperlyAdded())
{
// Special case, this one bumps us up to the next position
if (position == 0)
{
position = mPlayerPosition + 1;
if (position > 2)
position = 1;
}

switch (position)
{
case 1: // Stand, walk asd
{
RayInfo info;
Point3F curPos = getPosition();
curPos.z += 1.4;
Point3F nextPos = curPos;
nextPos.z += 0.4; // 1.8 - 1.4
if (getContainer()->castRay(curPos, nextPos, InteriorObjectType|StaticShapeObjectType|StaticTSObjectType, &info))
{
position = old_position;
return;
}
len_x = 1;
len_y = 1;
len_z = 1.8;
break;
}

case 2: // Crouch, sit
{
len_x = 1;
len_y = 1;
len_z = 1.4;
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;
onScaleChanged();
}

mPlayerPosition = position;
}

if (isServerObject()) setMaskBits(MoveMask);
}
#123
07/16/2007 (4:44 pm)
Quote:I am using TSE 1.5 and have the same problem that the camera does not follow the eye position with the animations. I can crouch and prone and see in third person it has succeeded but first person the camera stays the same height and the weapon models disapear unless i look down.
I have the same problem. It looks like the cam node hasn't been lowered for the crouching or crawling animations. Anyone have fixed versions?
#124
07/20/2007 (5:16 am)
I'm using TGE 1.5.1, it compiled fine but I get this error when loading a mission:


Debug Assertion Failed!
File: stricmp.c
Line: 98

Expression: dst != NULL

Any advice/help/fix?

---------------------------------

After trying to find the error for several hours, I had decided to take out swimming. This resolved to the bitstream error, which I easily fixed using the http://www.garagegames.com/mg/forums/result.thread.php?qt=5180 resource that was provided above.

Would be nice to get the swimming animations working correctly.
#125
08/08/2007 (6:50 pm)
I implemented this resource before, and it worked great!

Does anyone have a working link for the swimming animations? I lost almost all my work from my laptop that stopped working and the links on here seem to be broken.

If anyone can re-post the link for swimming that would be awesome.

Thanks!
#126
09/01/2007 (6:40 am)
When I try it, it compiles fine, but it just crashes when I go into the First Person view. Any ideas?
#127
09/02/2007 (9:57 am)
The prone animations dont work for me. The player shadow will change but its weird when it does. The crouch works fine but the guy cant go under lower things for some reason even though the bounding box changes. Also is there a fix anywhere to stay with the eye node?
#128
09/11/2007 (2:46 pm)
Has anyone got this to work with TGE 1.5.2?
#129
09/11/2007 (5:50 pm)
Hello. I had some troubles using this resource in my project (see here for details).
I uploaded a patch to apply on a clean 1.4.2 TGE install on my website here, and I left this resource's code commented out.
Can you give me some hints on what I done wrong? Thanks in advance for anything.

Bye, Berserk.
.
#130
10/02/2007 (4:05 am)
Thanks for this great resource! I made some changes to get the swimming part working in TGEA 1.0.3.
#131
10/27/2007 (6:45 am)
Awesome resource! Just need to work on decent animations, then I'll be finished! Oh, something else - when in observer camera, the player's default animation seems to play really slowly. Is this something to do with this resource? It may be interaction between this resource and the 'more realistic first person' resource I added, since the animation works fine in first person.
#132
11/17/2007 (9:34 pm)
I am now try to get this to work in v1.5.2 but I am getting the same error as Paul. Has anyone got this to work in 1.5.2?

Here is the error.

Debug Assertion Failed!
File: stricmp.c
Line: 98

Expression: dst != NULL
#133
11/18/2007 (11:31 am)
I figured out how to solve the error I was getting in 1.5.2. Swimming even works :)

I would have saved some time if I had read more closely what Javier Canon wrote but I ended up finding the solution through another resource.

Splitting Left and Right Strafe Animations
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2611

You must change the PlayerData::ActionAnimationDef array to include the new animations/actions. If you don't you will get the following error.

Debug Assertion Failed!
File: stricmp.c
Line: 98

Expression: dst != NULL
#134
12/12/2007 (10:41 pm)
Okay, so I put
ConsoleMethod( Player, setPlayerPosition, void, 3, 3, "(1=stand, 2=crouch)")
{
object->setPlayerPosition(dAtof(argv[2]));
}
at the end of player.cc and that made the unknown command error go away, but he still does not animate when I press "c". I am not using swim; so is my console method correct for that setup?
#135
12/13/2007 (8:19 am)
Try an put echo statements in each new script function on the client and server side to make sure everthing is being called.
#136
12/16/2007 (9:01 pm)
I put some echo statements in and it looks like
action = (mVelocity.len() < 0.5) ? PlayerData::CrouchRootAnim : PlayerData::CrouchForwardAnim;
is being called. I believe this is what makes the player go from standing to crouching.

When I press "c" the collision box toggles but the animations do not. If anyone has this working for 1.5.2 could you please post your code segments?
#137
03/07/2008 (4:24 pm)
All in all I think this was a great resource. One problem that I've had is getting the animations to work...not sure why. However the swimming works and I'm happy about that. For some reason I cannot get my character to crouch or crawl either.
#138
03/16/2008 (12:50 pm)
How can I get this to work on 1.5.2?
#139
03/21/2008 (9:33 pm)
Re-implemented the resource and it works great in 1.5.2.

Only problem is, the eye doesn't move with the rest of it.
#140
04/08/2008 (6:34 pm)
I got this is TGEA 1.7, but I cant get my players animations right. He will stay floating above the bounding box. How exactly did you guys animate your players for crouching and proning?