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]
#41
04/08/2005 (2:34 pm)
@Midhir
mObjBox.min.y = -mObjBox.max.y;
			mObjBox.min.z = 0;
			[b]onScaleChanged();[/b]
		}
		mPlayerPosition = position;

Meenwhile, Although the player clearly ducks, and can now get under small objects, the camera doesn't seem to follow suit. In my game, The player is never viewed from thrid person, so I havn't bothered with changing the orc. I've tried implementing the "Sitting" and "Scoutroot" animations that the orc has, but the camera never moves down. Though if someone has a solution, I would prefer not to muck around with animations. Has anyone else had this problem? Does anyone have a solution?
#42
04/18/2005 (9:28 pm)
Stupid solution, but works for me. Since I don't care about what the player looks like, I just change the scale of the player. that handles the camera without even implementing this code.
#43
04/22/2005 (7:37 pm)
Ok, got swimming to work pretty well in our game, but the speed at which the player can swim up and down is very slow. I was wondering what values I need to tweek to speed up vertical swim speed without affecting horizontal speed.
Would this be a gravity setting? Or perhaps a max vertical velocity problem? Could it even be as simple as changing the density or viscosity of the waterblock? Please help us out.

If you know the code to fix this, please post it along with the file name that code is in.

Thanks,
Jason Schaefer
#44
04/22/2005 (11:58 pm)
I got this rescource in with out much hasstle, it compiles and the game loads, but the player wont move, the mouse moves and shoots but thats it.
Any ideas where im going wrong?
#45
04/23/2005 (3:31 pm)
The player doesn't even move on land? If that is the case, then I'd say it's an issue with the setPlayerPosition function. You are probably stuck in a crouch or a crawl mode, but hadn't set up the movement characteristics for that yet or something. This is just a guess really, Im not a programmer.

If the issue is movement in water, then I'd check the density and viscocity of the waterblock, as well as the player's density and boyancy settings, etc.

Any ideas on my problem?

~Jason Schaefer
#46
07/04/2005 (5:27 pm)
bah nevermind, I fixed my problem...just for the record, the part where he says to change the code, that also includes the "addCommand" part. You don't need to do that anymore, as ConsoleMethod essentially "registers" itself. Since the addCommand parts were not italicized, I thought that meant I was supposed to keep them :)
#47
07/19/2005 (11:09 am)
Did anybody make any headway on the "swim-in-the-direction-you're-looking" thing?

Glez
#48
08/15/2005 (7:16 pm)
My game quits when i load it
#49
08/26/2005 (7:56 pm)
This is a really great resource. I'm working on some enhancements to add support for more movement animations, forcing the player to only swim on the water surface, and a fix for getDamageLocation to work with this code. When I'm done, I'll make sure to submit it as a resource.
#50
09/17/2005 (2:38 am)
this is great but i cant get:
crouch, jump and crawl.
i had plenty of compiling errors that I fixed but I still cant get these and after looking around I still cant find an answer
#51
09/17/2005 (5:52 pm)
Thanks Erik, great resource !!!
#52
09/23/2005 (9:19 am)
James I can not get my players to crouch either perhaps we are missing an animation?
#53
09/25/2005 (9:59 am)
Having a similar issue to Zuul here with the AssertFatal which is occuring after the mission starts:

"Fatal: (d:\torque\engine\math\mplane.h @ 233)"

Other forum posts seem to point towards collision (and user error) but i've tried stripping a map down to nothing but the terrain and skybox etc and it's the same.

Any ideas?

Thanks

InKy

| EDIT | It was a user error ;) For some reason i'd missed a couple of lines from the guide.
#54
10/02/2005 (9:43 pm)
@Jace:
You will need new animations with this resource.

I have added this resource to TGE 1.3 (LP 1.3.5) and it works great! But I have taken out the swim functions because I don't need them for my game.

Btw, Thanks Erik for this great resource.
#55
10/13/2005 (7:30 pm)
Hadn't seen anyone post a link to this here yet,

Here is a resource that ehances this one. Including making the bounding box size configurable from script.
#56
10/14/2005 (2:17 pm)
Heh, for some reason my player doesnt crouch all the time, only when running and even then he will pop up tp standing animation running and then alternate to crouch running while holding forward down.
#57
10/14/2005 (7:29 pm)
@Robert Pierce: I have the same problem. Freezes mid-animation whenever you let up on the arrow keys.

In reference to the player
Quote:...stops his animation midway "frozen". I am talking about walking/running.

If you move him somewhat to the left, he will not finish the left animation but instead stay at the current point in his current animation.
#58
10/22/2005 (4:50 pm)
Just curious if anyone else has been getting an "Invalid packet GameBase::unpackUpdate()" disconnect error message when loading the mission and after incorporating this resource. I have double checked the source twice and cannot find any errors in my code (although I'm sure I missed something).

I should mention that I do not have a model that includes the swim position and am wondering if this could be related.

Thanks,
John
#59
10/23/2005 (8:54 am)
Never mind, I re-implemented this using it against the 1.4 version and everything works perfectly on the first try.
#60
12/13/2005 (1:13 pm)
Nice resource - got this to work nearly straight away in 1.3 TGE (even though I had no graphics for the swim position animations!).

My only bug was caused my own typo - and this was easily found by running the debug version! If only I could read... And no I didn't need the commented out bit at the bottom (just like it says!)

Thanks Erik - keep up the good work.