Update: Go here:
http://forum.thegamecreators.com/?m=forum_view&t=159101&b=8
I did a search on here for jumping implementations, and at least from what I saw, not many people have figured out a solid way to smoothly jump with a character controller. If I'm wrong, let me know--I wanna read their code!. Either they use Sparky, DBP's own collision, or create their characters as dynamic rigid bodies and apply forces to them.
I like the last solution--having a character that interacts a lot with the physics of the environment can be a lot of fun in a game. However, sometimes you don't really want that. When reading the Information CHM about DarkPhysics, it seemed as though the whole advantage of a character controller was that it's detached from standard physics calculation, allowing more stable and controllable player movement. Personally, I'm trying to create a game in GDK with a style similar to N64 Zelda titles right now, and the character control in that game with regards to jumping has a sort of rigidity that makes the whole jumping experience seem more dramatic. Therefore, I was a little reluctant to leave my jumping control absolutely in the hands of DarkPhysics' dynamic rigid bodies.
So I learned from reading various morose threads that Character Controller gravity and jumping can be a little difficult to get right. Thankfully I found some invaluable posts. One was from Jammy, who posted about the "PHY MOVE CHARACTER CONTROLLER <objId>, 0.1 : PHY MOVE CHARACTER CONTROLLER <objId>, -0.1" trick after setting up displacement with "PHY SET CHARACTER CONTROLLER <objID>, 0, <backward#>". Also, I kept trying to use "PHY RAY CAST" when apparently you need to use "temp = PHY RAY CAST ALL SHAPES", and you can't specify whether you want static, or dynamic rigid bodies. (forgot who posted that one, couldn't find the thread, but thank you whoever you are!)
With the information above, it wasn't too terribly painful to write a smooth jumping implementation with smooth gravity. It's basically just high school physics, using the projectile equation.
y = 1/2*A*t + initialVelocity*t + initialY
dy/dx [velocity] = a*t + initialVelocity
The code is posted below, and uses a character model that should come default with a DBPro installation. If you have fixed the orientation of any of your sample DBPro models in an 3d editor, then go to the "setupScene()" function and comment out the XRotate and Fix Object Pivot statements. I tried to comment thoroughly, not line-per-line but giving overviews of how blocks of code work and why they're there. You'll notice there's sort of a double-check on the collision with the ground. The first part is a ray cast, the second part checks to see if the Y position has changed since the ray cast, and only then will enable the jumping = TRUE state. This will probably be problematic for capsule controllers--I have yet to test that. Why don't I just get rid of the ray cast check if I'm already checking Y positions? Well, it seemed safer to me, and now it may be easier to implement the "cliff-hanger" state; if the player is almost close enough to the ledge to fall then he will fall, but grab onto the edge, and the player must press a button to pull themselves up. I'll have to see. I also may be completely wrong on this, can anyone shed light?
If anybody finds any glitches or a faster/better way to do things, please post! It's been a while since I've written anything in DB.
Oh, and what is the deal with "Phy Set Character Controller Displacement <objId>, <x#>, <y#>, <z#>"? Is it just the POSITION OBJECT command for DarkPhysics?
Screenshot (because who doesn't love them?):
Code here:
#constant TRUE 1
#constant FALSE 0
//Standard setup stuff, I set sync rate to 60 because I didn't feel like implement timing yet
Sync On
Sync Rate 60
Autocam Off
Phy Start
Phy Enable Debug
//////////////////////////////////////////
// Adjustable Values //
//////////////////////////////////////////
global theColonel = 1 //Just a variable for the character's object ID, I hate writing object IDs
global jumpGravAccel# = 8.0 //This is the default acceleration of gravity
global playerJumpVelocity# = 300.0 //This is the default value for the player's upward velocity when jumping
global moveSpeed# = 85.0 //To change the character's move speed, change it here and recompile
global terminalVelocity# = 750.0 //To change the terminal velocity, fastest a player can fall, change here and recompile
global minimumRayCastDistance# = 85.0 //This value should be greater than or equal to the height of the player's character controller object
global camMoveSpeed# = 1.0 //To change the camera's movement speed, change this and recompile
global camTurnSpeed# = 1.0 //To change the camera's speed of rotation, change this and recompile
//////////////////////////////////////////
// Temporary Values //
//////////////////////////////////////////
global jumping as boolean = FALSE //When this is true, the character is either falling or jumping
global initialJumpVelocity# = 0.0 //This is used in calculating the next value to change the player's Y position with, when
// you jump this is set equal to playerJumpVelocity#, otherwise it equals 0
global deltaVerticalVelocity# = 0.0 //This is the current velocity, calculated from sim spent jumping, acceleration of gravity, and initial velocity
// Projectile formula -> y = 1/2*A*t^2 + initialVelocity*t + initialY
// Derived velocity formula -> deltaY = A*t + initialVelocity
global jumpTimeElapsed# = 0 //This value keeps track of time elapsed since gravity interaction (jumping or falling) began
global preJumpYPos# = 0.0 //This value keeps track of the previous Y position of the character after landing on the ground,
// this is necessary to detect when the player is near an edge, and raycasting becomes inaccurate
global maxY# = 0 //This is a statistical value used in the debug output--highest Y position of character
global maxVel# = 0 //This is a statistical value used in the debug output--highest downward velocity (falling)
global minVel# = 0 //This is a statistical value used in the debug output--highest upward velocity (jumping)
//////////////////////////////////////////
setupScene()
Position Camera 0, 100, -500
do
handleInput()
checkJumpOrFall()
printDebug()
Phy Update
Sync
loop
end
function handleInput()
Phy Set Character Controller Displacement theColonel, 0.0, 0.0 //Need to reset the gravity for regular movement
//Debug Information controls
if KeyState(59) then printControls()
if KeyState(19)
maxY# = 0
maxVel# = 0
minVel# = 0
Endif
//Jumping and gravity parameter controls
if Keystate(13) then jumpGravAccel# = jumpGravAccel# + 1
if Keystate(12) then jumpGravAccel# = jumpGravAccel# - 1
if KeyState(27) then playerJumpVelocity# = playerJumpVelocity# + 1
if KeyState(26) then playerJumpVelocity# = playerJumpVelocity# - 1
//Player controls
if KeyState(17) then Phy Move Character Controller theColonel, moveSpeed#
if KeyState(31) then Phy Move Character Controller theColonel, -moveSpeed#
if SpaceKey() then jump()
if KeyState(30) then Turn Object Left theColonel, 1
if KeyState(32) then Turn Object Right theColonel, 1
if KeyState(16)
Turn Object Left theColonel, 90
Phy Move Character Controller theColonel, moveSpeed#
Turn Object Right theColonel, 90
endif
if KeyState(18)
Turn Object Right theColonel, 90
Phy Move Character Controller theColonel, moveSpeed#
Turn Object Left theColonel, 90
endif
//Camera Controls
Control Camera Using Arrowkeys 0, camMoveSpeed#, camTurnSpeed#
if ReturnKey() then Position Camera Camera Position X(), Camera Position Y()+camMoveSpeed#, Camera Position Z()//camera up
if ShiftKey() then Position Camera Camera Position X(), Camera Position Y()-camMoveSpeed#, Camera Position Z()//camear down
if KeyState(51)
Turn Camera Left 90
Move Camera camMoveSpeed#
Turn Camera Right 90
endif
if KeyState(52)
Turn Camera Right 90
Move Camera camMoveSpeed#
Turn Camera Left 90
endif
Endfunction
function jump()
if not jumping
jumping = TRUE
initialJumpVelocity# = playerJumpVelocity#
endif
endfunction
function checkJumpOrFall()
////////////////////////////////////////////////////////////////////////
//This block of code controls falling. If the player has jumped,
// then the initialVelocity# variable is set to the playerJumpVelocity#
// value. Otherwise, initialJumpVelocity# will be 0, and the player
// will just fall. Gravity for a character controller can be controlled
// using Phy Set Character Controller Displacement <objID>, <velocityMovingForward>, <velocityMovingBackward>
// However, this velocity will only take effect when the player moves,
// so I borrowed Jammy's idea of moving the controller forward and then
// back a bit so DarkPhysics can register movement and apply the gravity.
// The variable jumpTimeElapsed# is the representation of t in the equations:
// y = 1/2*A*t^2 + initialVeloity*t + initialY
// and
// dy/dt(or velocity) = a*t + initialVelocity
//In order to properly implement this solution, it would be a good idea
// to implement a timing system.
////////////////////////////////////////////////////////////////////////
if jumping = TRUE
text 3, 340, "THOU HAST JUMPED"
deltaVerticalVelocity# = jumpTimeElapsed# * jumpGravAccel# - initialJumpVelocity#
if deltaVerticalVelocity# > terminalVelocity# then deltaVerticalVelocity# = terminalVelocity#
Phy Set Character Controller Displacement theColonel, 0.0, deltaVerticalVelocity#
Inc jumpTimeElapsed#, 1
else //Right now, this else statement exists solely for the purpose of observing the transition to the "not jumping" state
text 3, 340, "NOT JUMPING"
endif
Phy Move Character Controller theColonel, 0.1
Phy Move Character Controller theColonel, -0.1
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//All of the code in this block determines if the character controller
//has collided with the floor beneath him, or has decied to walk off an
//edge and started to fall.
////////////////////////////////////////////////////////////////////////
x# = Object Position X(theColonel)
y# = Object Position Y(theColonel)
z# = Object Position Z(theColonel)
//First we cast a ray down from the origin of the character controller
hit = Phy Ray Cast All Shapes(x#, y#, z#, 0,-1,0)
dist# = Phy Get Ray Cast Distance()
zscl# = Object Size Z(theColonel)
text 3, 300, "Ray Cast Distance = "+Str$(dist#)+" cast="+Str$(hit)+" Collision="+Str$(Phy Get Collision Count(theColonel))
//--------------------------------------------------------------------
//If the raycast has hit, Phy Ray Cast All Shapes will return a 1.
//So we check, if the ray hit an object AND the distance is less
// than a specific threshold we set (minimumRayCastDistance#
// is the distance from the origin of the character model to the
// bottom of the character controller collision box/capsule. If so,
// then we know we are standing on the ground. Otherwise, we must
// be jumping or falling.
//I don't know how to accurately determine the ray cast threahold
// programmatically, so I just observed my ray cast distance output,
// and played with some values until I found a good one. If you
// find that your character controller tends to do just a little hop
// unless you hold down the jump key for a half second, then your
// minimumRayCastDistance# may be set too high.
//--------------------------------------------------------------------
if hit = TRUE and dist# < minimumRayCastDistance#
jumping = FALSE //This will disable the
jumpTimeElapsed# = 0
deltaVerticalVelocity# = 0.0
initialJumpVelocity# = 0.0
preJumpYPos# = Object Position Y(theColonel) //This is used for the code block below
text 3, 360, "Ray cast says we're on the ground"
else
if hit = FALSE or dist# > minimumRayCastDistance#
jumping = TRUE
text 3, 330, "STARTED FALLING"
endif
endif
//--------------------------------------------------------------------
//This code block is necessary, because although ray casting can do a
// great job of determining is an objects within a certain threshold
// of another object beneath it, what happens when you're just barely
// standing over the edge? Well, comment out this block and you'll see
// that the ray cast will think you are falling, because your point of
// origin _is_ hanging out over the edge, but the edges and corners of
// the character controller box are still hanging on. As such, the timer
// will starting iterating over its series of values, and your deltaVerticalVelocity#
// will keep increasing. Then, if you do actually step off the edge you
// will plummit downward at incredible speeds. To fix this, the code below
// will look at the preJumpYPos# value that was set before, and if it hasn't
// changed, then it will recognize that and stop the jump/fall procedure.
//It might seem redundant to do a collision check using BOTH ray casts
// and tracking change in Y position, but what if you want to have your
// character be able to slip off of the edge of the cliff? Having two
// different states of "falling" and "falling, but not really just kidding"
// may make it easier to imeplement this.
//Note: This implementation may prove difficult for Capsule character controllers
// due to the fact that your Y position _may_ actually change before you really
// begin falling due to the capsule's curved bottom. I haven't played around
// with this yet, but it will likely be a matter of having a small range
// in which the player is allowed to move up or down before actually
// allowing the jump = TRUE state to persist.
//--------------------------------------------------------------------
if jumping = TRUE and preJumpYPos# = Object Position Y(theColonel)
jumping = FALSE
jumpTimeElapsed# = 0
deltaVerticalVelocity# = 0.0
initialJumpVelocity# = 0
text 3, 360, "Ray cast reports falling, but we're not really falling, maybe cliff-hanging?"
endif
////////////////////////////////////////////////////////////////////////
endfunction
function setupScene()
Color Backdrop rgb(0,0,0)
Ink rgb(100, 200, 50), 0
Make Object Box 3, 500, 10, 500 //This is the box representing the upper floor
Make Object Box 4, 500, 10, 500 //This is the box representing the lower flor
Position Object 4, -300, -500, 0 //Position the lower floor so we can jump down onto it
Phy Make Rigid Body Static Box 3 //Now, make them static rigid bodies, we don't
Phy Make Rigid Body Static Box 4: // want the floor falling beneath us
mediaPath as String = "C:/Program Files/The Game Creators/Dark Basic Professional/Media/"
Load Object mediaPath+"Models/Colonel Z/ColZ.x", theColonel
XRotate Object theColonel, -90
Fix Object Pivot 1 // The model's default rotation is off by 90 deg on the X axis
Offset Limb theColonel, 0, 0,0, -Object Size Y(theColonel) / 2.0 //If you don't set the origin of the model to the approximate
//center of the model, then your character controller object
//will be displaced. The default for ColZ.x is to have the origin
//at his feet. Now including this statement makes it look as though
//he floats a few feet in the air where ever he goes.
Position Object theColonel, 0, 500, 0
x# = Object Position X(theColonel)
y# = Object Position Y(theColonel)
z# = Object Position Z(theColonel)
yscl# = Object Size Y(theColonel) //Need these size values for creating the character controller
xscl# = Object Size X(theColonel)
zscl# = Object Size Z(theColonel)
//When making the character controller box or capsule, I had to play around a bit to find good values
//for the Y values. Object Size Y() ended up returning a value that was way too big (possibly because
//of the limb offset command?), so I found adding an offset to the Z size worked well enough for Y.
Phy Make Box Character Controller theColonel, x#, y#, z#, xscl#, zscl#+5.0, zscl#, 1, 1.5, 45.0
endfunction
function printDebug()
statistics()
Text 3, 3, "ColZ-X= "+Str$(Object Position X(theColonel))+" ColZ-Y= "+Str$(Object Position Y(theColonel))+" ColZ-Z= "+Str$(Object Position Z(theColonel))
Text 3, 3+Text Height("A"), "ColZ-Xrot= "+Str$(Object Angle X(theColonel))+" ColZ-Yrot= "+Str$(Object Angle Y(theColonel))+" ColZ-Zrot= "+Str$(Object Angle Z(theColonel))
Text 3, 3+Text Height("A")*3, "Cam-X= "+Str$(Camera Position X())+" Cam-Y= "+Str$(Camera Position Y())+" Cam-Z= "+Str$(Camera Position Z())
Text 3, 3+Text Height("A")*4, "Cam-Xrot= "+Str$(Camera Angle X())+" Cam-Yrot= "+Str$(Camera Angle Y())+" Cam-Zrot= "+Str$(Camera Angle Z())
Text 3, 3+Text Height("A")*6, "FPS: "+Str$(Screen FPS())
Text 3, 3+Text Height("A")*7, "Jump Velocity= "+Str$(playerJumpVelocity#)+" Gravity Acceleration = "+Str$(jumpGravAccel#)
Text 3, 3+Text Height("A")*8, "jumpTimElapsed# = "+Str$(jumpTimeElapsed#)+" jumping = "+Str$(jumping)+" deltaVerticalVelocity = "+Str$(deltaVerticalVelocity#)
Text 3, 3+Text Height("A")*9, "STATS: MaxVelocity="+Str$(maxVel#)+" MinVelocity="+Str$(minVel#)+" MaximumYPos="+Str$(maxY#)
Text 3, 450, "Press 'F1' for controls"
endfunction
//Just calculates minimum and maximum values for displaying on screen
function statistics()
y# = Object Position Y(theColonel)
if deltaVerticalVelocity# > maxVel# then maxVel# = deltaVerticalVelocity#
if deltaVerticalVelocity# < minVel# then minVel# = deltaVerticalVelocity#
if y# > maxY# then maxY# = y#
Endfunction
function printControls()
CLS
print "Controls:"
print "Move player back/forward: W and S"
print "Turn player left/right: A and D"
print "Strafe player left/right: Q and E"
print "Jump: Space"
print
print "Move Camera Forward/Back: Up-Arrow and Down-Arrow"
print "Move Camera Up/Down: Enter and Shift"
print "Strafe Camera Left/Right: , and ."
print
print "Increase/Decrease acceleration of gravity: = and -"
print "Increase/Decrease player's jump velocity: } and { (curly brackets)"
print "Reset minimum/maximum statistics: R"
print
print "Display this screen: F1"
print
print "Press the 'Any' key to continue"
Sync
wait key
Endfunction