Posted by: hopper
Date posted: Jan 08 2005 User Rating: 5 out of 5.0 | Number of views: 72008 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.
| | class CSDKGameMovement : public CGameMovement { public: DECLARE_CLASS( CSDKGameMovement, CGameMovement );
CSDKGameMovement(); //overload CGameMovement::CheckJumpButton bool CheckJumpButton();
|
2. Add the function body below the CSDKGameMovement constructor.
| | 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:
| | 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:
| | 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.
| | 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:
| | //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:
| | //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:
| | // 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.
| | 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.
| | #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 |
|
User Comments
Showing comments 1-10
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? |
|
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. |
|
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. |
|
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 |
|
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 |
|
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
|
|
dose with work with ibutsu's walljumping code? |
|
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. |
|
brings back memories of my mega man x mods lol |
|
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.
|
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (8 guests)
|
|