Welcome, Guest! Login | Register

Game Movement Series #2: Analog Jumping and Floating [Print this Article]
Posted by: hopper
Date posted: Jan 08 2005
User Rating: 5 out of 5.0
Number of views: 60906
Number of comments: 10
Description: How to set up analog jumping (the longer you hold jump, the higher you go) and floating (pressing jump as you are falling decelerates your fall)
To continue Ibutsu's series on Game Movement, I take a look at setting up an analog jumping system.

This tutorial details how to incorporate analog "float" jumping into your mod. The idea behind this jump style is that the longer you hold down jump, the higher you rise into the air. After a specified period of time, you can no longer go up, and you will start to fall. As you are falling, if you release the jump key and hit it again, you will begin to decelerate until you reach a minimum fall velocity, at which point you will sustain that fall rate as long as jump is held down. By changing one line of code, you can alternate between allowing the user to "catch" his/her self once or as many times as he/she wants.


First we will overload CGameMovement::CheckJumpButton function in the CSDKGameMovement class.

1. In sdk_gamemovement.cpp, declare the function bool CheckJumpButton(); in the public section of the class.

 CODE  

class CSDKGameMovement : public CGameMovement
{
public:
   DECLARE_CLASS( CSDKGameMovement, CGameMovement );

   CSDKGameMovement();
   //overload CGameMovement::CheckJumpButton
   bool CheckJumpButton();



2. Add the function body below the CSDKGameMovement constructor.

 CODE  

bool CSDKGameMovement::CheckJumpButton( void )
{
}



Ok, that parts done. Now we need to start adding some actual code.


3. First, we need to declare some variables we are going to be using for our jump code. Add the following variables to the private section of the CSDKGameMovement class declaration in sdk_gamemovement.cpp:

 CODE  

private:
   
   float   m_fLastJumpTime;         //the last jump time (seconds)
   float   m_fLastTimeThrough;      //the last time we went through this code
   int     m_iNumJumps;             //the number of times we've hit jump in this series of jumps
   bool    m_bFirstTimeThrough;     //is this the first time through this func for this press of jump

   bool    currInAir;               //are we currently in the air
   bool    prevInAir;               //were we in the air last time through?
   bool    newJump;                 //is this a new jump?
   bool    supressJump;             //should i shortcircuit out of this jump?



The comments attempt to describe the purpose of each of the member variables, but dont worry about it that much quite yet.


4. Next we want to initialize all that stuff in the CSDKGameMovement constructor. So still in sdk_gamemovement.cpp, edit the constructor to look like this:

 CODE  

CSDKGameMovement::CSDKGameMovement()
{
   //initialize stuff
   m_iNumJumps = 0;            
   m_bFirstTimeThrough = true;  
   m_fLastJumpTime = -1;
   m_fLastTimeThrough = -1;
   currInAir = false;
   prevInAir = false;
   newJump = false;
   supressJump = false;
}




5. Ok, the next step is to actually start coding this thing. First we need some basic jump handling logic (ie, what to do if we're dead, in water, on a ladder, etc). Basically, we're copying and pasting this code from CGameMovement::CheckJumpButton(), but here it is in a slightly more compressed format.

 CODE  

bool CSDKGameMovement::CheckJumpButton( void )
{
   //if dead, kill this jump
   if(player->pl.deadflag)
   {
      mv->m_nOldButtons |= IN_JUMP;
      m_iNumJumps = 0;
      return false;
   }

   // See if we are waterjumping.  If so, decrement count and return.
   if (player->m_flWaterJumpTime)
   {
      player->m_flWaterJumpTime -= gpGlobals->frametime;
      if (player->m_flWaterJumpTime < 0)
         player->m_flWaterJumpTime = 0;

      m_iNumJumps = 0;
      return false;
   }

   // If we are in the water most of the way...
   if ( player->GetWaterLevel() >= 2 )
   {  
      // swimming, not jumping
      SetGroundEntity( (CBaseEntity *)NULL );

      if(player->GetWaterType() == CONTENTS_WATER)    // We move up a certain amount
         mv->m_vecVelocity[2] = 100;
      else if (player->GetWaterType() == CONTENTS_SLIME)
         mv->m_vecVelocity[2] = 80;

      // play swiming sound
      if ( player->m_flSwimSoundTime <= 0 )
      {
         // Don't play sound again for 1 second
         player->m_flSwimSoundTime = 1000;
         PlaySwimSound();
      }
      m_iNumJumps = 0;
      return false;
   }

   // Don't allow jumping for the following
   if ( player->m_Local.m_bSlowMovement
      || (player->m_Local.m_bDucking && (  player->GetFlags() & FL_DUCKING ))
      || ( player->m_Local.m_flDuckJumpTime > 0.0f ))
   {
      m_iNumJumps = 0;
      return false;
   }


}



The comments explain what is going on in there. Essentially, we are preventing jumping in some cases, handling jumps from underwater and in the water, and when you're in the proccess of ducking.


6. Next, there are a few things we need to be able to do to make float jumping work right.

-We need to know which calls to this function are the result of a new press of the jump button, and we need to know what time these calls occur at, AND we need to know how many times jump has been pressed. At the end of the CheckJumpButton function, add the following code:

 CODE  

   //if it has been more than two frames since the last time this function was called, this is a new jump
   if(gpGlobals->curtime - m_fLastTimeThrough > 2*gpGlobals->frametime)
      newJump = true;
   else
      newJump = false;

   //if this was a new jump, incrememt the jump count, make it first time through, and log the time
   if(newJump)
   {
      m_iNumJumps++;
      m_bFirstTimeThrough = true;
      m_fLastJumpTime = gpGlobals->curtime;
      m_fLastTimeThrough = gpGlobals->curtime;
   }




-We need to know the current and previous state of the player being on the ground or in the air. Right after the above code, add the following:

 CODE  

   //Determine the inAir state
   prevInAir = currInAir;
   currInAir = (player->GetGroundEntity() == NULL);




7. Ok, now its time to sort out the various jump cases. Here it is with tons of comments and an explanation of whats happening at the end:

 CODE  

   // are we in the air?
   if (currInAir)
   {
      //if we are still rising, and its past our time limit for rising, then stop rising
      if((m_iNumJumps == 1 && gpGlobals->curtime - m_fLastJumpTime > JUMPLIMIT))
      {
         mv->m_nOldButtons |= IN_JUMP;
         return false;  
      }
   }
   else   //we are on the ground so we can jump!
   {
      if(prevInAir)
      {
         if(!newJump)
         {
            //if we were in the air and this isnt a new jump, you're gonna pogo jump
            //unless we do something about it.  So we will supress the next jumps
            mv->m_nOldButtons |= IN_JUMP;
            supressJump = true;   //tells us to supress the pogo jumping
            m_iNumJumps = 0;
            prevInAir = false;
            return false;
         }
      }

      //sanity check.  if we're not in the air and you're jumping then this is your first jump
      m_iNumJumps = 1;
      prevInAir = false;

      //play sound and animation for jumping off ground
      player->PlayStepSound( mv->m_vecAbsOrigin, m_pSurfaceData, 1.0, true );  
      MoveHelper()->PlayerSetAnimation( PLAYER_JUMP );
   }

   //if we were supposed to supress pogo jumps
   if(supressJump)
   {
      //only supress until the last button wasnt jump
      if(mv->m_nOldButtons & IN_JUMP)
         return false;
   }
   //when the user lets go of jump and hits it again, the code will fall through to here and we wont supress anymore
   supressJump = false;

   // In the air now, so we dont have a ground entity
   SetGroundEntity( (CBaseEntity *)NULL );

   //figure out if the ground will effect our jump
   float flGroundFactor = 1.0f;
   if (m_pSurfaceData)
      flGroundFactor = m_pSurfaceData->game.jumpFactor;

   // If we are ducking, only allow a normal jump
   float startz = mv->m_vecVelocity[2];
   if ( (  player->m_Local.m_bDucking ) || (  player->GetFlags() & FL_DUCKING ) )
   {
      //only allow jumping once
      if(m_iNumJumps <= 1)
         mv->m_vecVelocity[2] = flGroundFactor*JUMP;
   }
   else
   {
      //we arent ducking, this is the second time we hit jump, so go into float mode
      if(m_iNumJumps > 1)
      {
         //if we get within +-50 of our terminal velocity, just start going that speed
         float diff = MAXFALLRATE - mv->m_vecVelocity[2];
         if(diff < 50 && diff > -50)
         {
            mv->m_vecVelocity[2] = MAXFALLRATE;
         }
         //only reduce fall rate if we're falling faster than the max fall rate
         else if(mv->m_vecVelocity[2] < MAXFALLRATE)
         {
            //decelerate us, taking the tick rate into consideration (cpu speed wont effect rate of fall/rise)
            //the *100 is just cause frametime is REALLY small
            mv->m_vecVelocity[2]  += (FALLDECEL*gpGlobals->frametime)*100;  
            //dont want to shoot up over maxfallrate
            if(mv->m_vecVelocity[2] > MAXFALLRATE)
               mv->m_vecVelocity[2] = MAXFALLRATE;
         }
      }
      else
      {
         //if this is the first jump vs the sustaining jumps
         if(m_bFirstTimeThrough)
            mv->m_vecVelocity[2] += (flGroundFactor * JUMP);  //regular jump to get us up quick
         else
            mv->m_vecVelocity[2] += (RISEACCEL*gpGlobals->frametime)*100;  //accelerate up
      }
   }

   //allow gravity to do its thing
   FinishGravity();

   //stick the results of our meddling into the jump results vector
   mv->m_outJumpVel.z += mv->m_vecVelocity[2] - startz;
   mv->m_outStepHeight += 0.15f;

   // Flag that we jumped.
   mv->m_nOldButtons |= IN_JUMP;
   //its no longer our first time through
   m_bFirstTimeThrough = false;
   m_fLastTimeThrough = gpGlobals->curtime;

   return true;




Ok, so what we're doing is checking to see if we're in the air or not. If we are in the air we're either going to be continuing upward, or floating downward. If we're going up, and we're past the time limit on going up, we cant keep going up. If we're on the ground, then we are allowed to start jumping from scratch. Pogo jumping is very irritating, so there is special code in there to handle it.

So thats it, now you need to do a few things to get this thing to compile, so onward!...


8. We need to declare CSDKGameMovement a friend class of C_BasePlayer so that we can access the private members (/me chuckles) of the C_BasePLayer class . To do this, go into c_baseplayer.h and search for statement "friend class CGameMovement" Right below that series of friend class declarations, add CSDKGameMovement as a friend class.

 CODE  

friend class CSDKGameMovement;




9. Finally, you have to stick the different vars I was using for determining acceleration, deceleration, jump limit, etc. So go to the top of sdk_gamemovement.cpp and add the following. The values associated with each #define are the ones I have been playing with lately, but feel free to experiment.

 CODE  

#define JUMP               200.0  //how much to jump, normal is a bit more than this (~260?)
#define RISEACCEL          15.0   //rise acceleration
#define MAXFALLRATE       -240.0  //max fall rate, true fall rate will be a little more than this because of gravity
#define FALLDECEL          25.0   //deceleration, units? haha...
#define JUMPLIMIT            0.8  //time limit in seconds



Well congratulations. Thats it. You're done. I have to go now... I think I have a... Food... in the oven.

-Hopper-
Mind-Body-Power, a HL2 mod
www.mind-body-power.net

Rate This Article
This article is currently rated: 5 out of 5.0 (2 Votes)

You have to register to rate this article.
User Comments Showing comments 1-10

Posted By: jediborg on Feb 07 2005 at 04:03:32
very thourough hopper,
if your trying to make a hl2 mod in singleplayer though, you've got to mod the gamemovement.cpp file, NOT the sdk_gamemovement.cpp
the gamemovement.h file has some of its own variables simillar to your declarations, like RISEACCEL, JUMPLIMIT, ect. how will these variables interfere with your code?

Posted By: hopper on Feb 09 2005 at 15:03:24
I wrote this for the mp sdk, I should probably have noted that at the top.

I will be getting the sp sdk as soon as my cable company decides to fix my high speed connection. Then I can take a look and let you know.

Posted By: Whiplash on Feb 23 2005 at 17:49:01
After following your walkthrough word for word, I'm coming up with this error:
C2248: 'CBasePlayer::m_flWaterJumpTime' : cannot access private member declared in class 'CBasePlayer'
I'm trying to fix this, but I can't see a way around it... suggestions are welcome.

Posted By: hopper on Feb 23 2005 at 23:12:26
step 8 is supposed to address this issue. If you are using the start a mp mod from scratch option, you want the statement to look like it does in step 8: "friend class CSDKGameMovement;" If you are using the hl2:dm mod option, you want to add your analog jumping code to the CheckJumpButton code in the gamemovement.cpp file, and you want the step 8 part to read like this : "friend class CGameMovement;"

The reason you get this error is because the CheckJumpButton function in the CGameMovement class is trying to access the private member m_flWaterJumpTime from CBasePlayer. By declaring CGameMovement a frienc class of CBasePlayer, you are granting it access to its private members.

-hopper

Posted By: hopper on Feb 24 2005 at 03:52:45
To use this tutorial with the new HL2:DM source code, do the following:

skip step 1
skip step 2
for step 3, the CGameMovement class is in gamemovement.h (under the headers folder) Do this step in the class declaration in gamemovement.h
steps 4, 5, 6, 7, and 8should still be the same, only substitute CGameMovement for CSDKGameMovement
for step 9, put the #define statements in gamemovement.h with the other #defines that are already there

when I get a chance, I'll prob just port the whole tut over to work with the new sdk source.
-hopper

Posted By: hopper on Feb 25 2005 at 03:57:35
Correction to the tutorial:
You need to also add the friend class declaration to the player.h file, same deal... search for the other friend class declarations...Edited by hopper on Feb 25 2005, 04:10:51

Posted By: eljay_692005 on Jan 22 2006 at 04:27:47
dose with work with ibutsu's walljumping code?

Posted By: hopper on May 22 2006 at 18:10:56
I originally implemented this as part of a three class gameplay system where each class had a different jumping style. One had wall jumping, one had double jumping (a la metroid prime double jump boots) and one had this analog jumping. So, the quick answer is yes, it can work with the wall jumping code if you were only trying to do one jump style at a time. However, if you wanted to do both wall jumping and float jumping at the same time, it would get a bit tricky (and it certainly wouldnt be a copy-paste kind of deal.

Posted By: omega on Jun 20 2006 at 15:15:07
brings back memories of my mega man x mods lol

Posted By: mars3554 on Oct 26 2009 at 04:17:39
Really Really Really Awesome!

People: Read Hopper's post @ Feb 23 2005 21:52:45 !!!!!

The only thing I'd add is

In gamemovement.h, under protected, add:

surfacedata_t* m_pSurfaceData;

Then it compiles w/o a hitch. Speaking of megamanX, where's our wall slide+jump!??!


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Persuter | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (7 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!