-
Notifications
You must be signed in to change notification settings - Fork 8
physics_process()
This is the meat of the script.
FYI, just in case you don’t know, “_physics_process()” is called a certain amount of times per second according to the project’s “Physics FPS” option in “Project Settings”.
Let’s get into it.
We start off by getting info about the current state of the player, which is what you always want to do at the start of a loop, any kind of loop.
State_OnFloor = is_on_floor()
State_OnWalls = is_on_wall()
State_OnCeiling = is_on_ceiling()
SlideCount = get_slide_count()
Player_Position = translation
The first thing we need to do with this script is get information. Getting information is half of what this script does. Only a few lines actually do anything, like move the character.
In the first few lines we check the wall, floor, and ceiling states. Then we get the slide count and set the player’s position variable.
DirectionInNormalVec3_FWAndBW = get_global_transform().basis.z
DirectionInNormalVec3_LeftAndRight = get_global_transform().basis.x
These variables hold the forward and sideways normals, for use with horizontal movement, including strafing.
“DirectionInNormalVec3_FWAndBW” gets the forward normal of the player character. That is, the normal that tells us which direction the player is facing. Note that this represents just the player’s kinematic body, and not the camera. So if you are looking up or down, this normal will still be horizontal.
It just so happens that in order to find out the direction that our player is facing, all we need to do is get the player’s Z axis transform normal. This can be visualized as such:
For information on how normals work go to the “Normals Explained > Vectors and Normals > Normals” section.
In this picture, the thing we are needing is the blue arrow, which represents the Z axis normal, the direction the player is facing. Also note that these normals are in local space.
It’s the same way with “DirectionInNormalVec3_LeftAndRight”. All we need to get is the player’s X axis transform, the red arrow.
Be sure to check out the “Normals Explained > Transforms” chapter for information on what transforms are. The X axis of the vector would look like this:
In this picture, the X axis is highlighted. So the “x” we are getting is the normal’s coordinates.
The X normal that is circled here has this position:
X_Normal = (-0.914192 , 0.0 , 0.405281)
These two vectors will be used later in the “_physics_process() > HORIZONTAL MOVEMENT” section.
Now we are moving into the “INPUT” section. The first few lines are:
Pressed_FW = Input.is_action_pressed(String_FW)
Pressed_BW = Input.is_action_pressed(String_BW)
Pressed_LEFT = Input.is_action_pressed(String_Left)
Pressed_RIGHT = Input.is_action_pressed(String_Right)
This simply gets the current input states of the horizontal movement actions. They are just bools.
if(Input.is_action_pressed(String_Jump)):
Pressed_Jump = true
if((State_Jumping or State_Falling) and not State_OnFloor):
Jump_Released = false
else:
Pressed_Jump = false
Jump_Released = true
I’m going to start concatenating the code examples from now on for expediency.
This code block takes care of the jump action. It first checks to see if the jump action is pressed. If it is it sets the “Pressed_Jump” bool to true. Now the next part in that "if" statement checks if the player is even on the ground (that he is falling or jumping). If he’s not on the floor he can’t jump, so the “Jump_Released” bool, which allows or denies a jump based on its state, is set to false so as to not allow a jump to occur in midair.
Otherwise, if the jump key is not being pressed then we say that the jump key is not being pressed and that it has been released, so that the player can now jump again.
if(Input.is_action_pressed(String_Shift)):
FinalWalkVelocity = BaseWalkVelocity * ShiftWalkVelocity_Multiplier
else:
FinalWalkVelocity = BaseWalkVelocity
Here we have the speed shift action. If the “Shift” key or the shift speed action is being pressed, we multiply the player’s walk velocity according to “ShiftWalkVelocity_Multiplier”. By default “ShiftWalkVelocity_Multiplier” is “2.0” and the base walk velocity is “10”. So that means if the shift action is pressed the final velocity will be “20”, so that the player can move faster.
Step_SafetyMargin =
Step_SafetyMargin_Dividend / (BaseWalkVelocity / FinalWalkVelocity)
Here we modify the safety margin of the player’s stepping function. This makes the player move up a little past the step to make sure the player actually gets onto the step instead of just the corner, resulting in the player getting stuck on the side of the step.
We do this by dividing the base walk velocity, “10”, by the final walk velocity, which we’ll say is “20” in this example. The result is “0.5”.
Then we divide“Step_SafetyMargin_Dividend” by that result, resulting in “0.04”. If the final walk velocity was “10” the ultimate result would be “0.02”. Evidently this is because when moving more quickly it’s necessary to make sure the player goes up the step farther to keep him from hitting the edge. I’m honestly not sure, though, but it works.
CamInterpo_Length_Secs = BaseWalkVelocity /
( CamInterpo_Length_Secs_Multiplicand * (FinalWalkVelocity / 10.0) )
Here we take care of the camera’s interpolation length. This way when the player is moving faster, the camera is interpolated faster to keep up with the player.
It sets the length, in seconds, by first dividing the final walk velocity by 10. Let’s say that the final walk velocity is “20” because the player has the shift speed action pressed.
FinalWalkVelocity / 10.0 = 2.0
Then we multiply “CamInterpo_Length_Secs_Multiplicand” by that result. “CamInterpo_Length_Secs_Multiplicand” is “125.0”. So that means the product will equal “250.0”.
Lastly we divide the base walk velocity by that, resulting in “0.04” seconds, which is our new camera interpolation length. If the final walk velocity was “10” then our interpolation length would equal “0.08” seconds. This is longer because the player is walking up the stairs slower and therefore the camera doesn’t need to interpolate as fast.
Ray_SpaceState = get_world().get_direct_space_state()
Ray_From = Node_Camera3D.get_global_transform().origin
Ray_To = -Node_Camera3D.get_global_transform().basis.z * Ray_UseDist + Ray_From
Ray_Result = Ray_SpaceState.intersect_ray(Ray_From,Ray_To,[self])
Now we are entering the section that takes care of the “Use” action and the red circle that shows up whenever the player is looking at something he can interact with.
The first thing we do is setup a ray cast and then execute it. It starts from the player’s camera and goes along the direction that it’s pointing. This goes as far as “Ray_UseDist” is set, which by default is “2.0”. “Ray_From” is added as the offset of the vector, giving it a global position.
As a side note, the last option in the “intersect_ray()” function is any objects or nodes that you want the ray cast to ignore. If you put in “[self]”, this not only includes the kinematic body that this script is connected to but all its child nodes.
if(not Ray_Result.empty()):
We check if the ray hit anything. If it did we go on to this code, here:
if(Ray_Result.collider.has_method("UseFunction")):
Node_Crosshair_Useable.visible=true
if(Input.is_action_just_pressed(String_Use)):
Use_Ray_IntersectPos = Ray_Result.position
Ray_Result.collider.UseFunction()
If the collider of the ray cast has a function named “UseFunction”, we set the red circle node, which is the “Camera2D/Crosshair/Crosshair_Useable” node in the player scene, to be visible.
Then we check to see if the “Player_Use” action is being pressed. If it is we simply execute the “UseFunction()” function in the collider’s script.
elif(Ray_Result.collider.get_parent().has_method("UseFunction")):
Node_Crosshair_Useable.visible=true
if(Input.is_action_just_pressed(String_Use)):
Use_Ray_IntersectPos = Ray_Result.position
Ray_Result.collider.get_parent().UseFunction()
Otherwise, if the collider doesn’t have a function, then we check to see if the collider’s immediate parent has one. This is a “just in case” scenario and may never be executed. That’s why I put the previous "if" statement first, as it’s more likely to happen and therefore won’t have to go though multiple "if" statements to check for a “UseFunction()” function.
In any case we do exactly the same thing here that we did in the previous "if" statement, but on the parent of the collider.
Note that the “collider” is always the physics body that holds the collider shape, not the shape itself. So if you had a rigid body that had a sphere collision shape, the collider returned would be the rigid body, not the shape.
# Otherwise, if there is no use function...
else:
Node_Crosshair_Useable.visible=false
Use_Ray_IntersectPos = Vector3(0,0,0)
This “else” statement is for when there isn’t any “Use” function in the collider. If that’s the case, we simply hide the red circle and set the intersection point of the “Use” ray to a 3D vector that is all 0’s. This is so no accidental actions or incorrect impulse positions occur because of a left over variable. Also this may help prevent a wrong return variable when an external script tries accessing “Use_Ray_IntersectPos”. This may not actually do anything, but I’m not totally sure.
# Else, if the ray hit nothing...
else:
Node_Crosshair_Useable.visible = false
Use_Ray_IntersectPos = Vector3(0,0,0)
This last part of this section happens if there isn’t any intersection of the ray cast. It hides the red circle and set the intersection point of the “Use” ray to a 3D vector that is all 0’s, like in the previous code block.
Now we get information on the horizontal movement by the player and set the velocity of such.
First off let’s take a look at the “if” and “else” statements in the “FORWARD AND BACKWARDS CALCULATIONS” section:
if(Pressed_FW and not Pressed_BW):
# Move forward.
elif(not Pressed_FW and Pressed_BW):
# Move backward.
elif(Pressed_FW and Pressed_BW):
# Don’t move forward or backward.
elif(not Pressed_FW and not Pressed_BW):
# Don’t move forward or backward.
else:
# Don’t move forward or backward. This is a fail safe case.
Here we have the basic action taken in each “if” or “else” statement. Let’s go through each case.
First, we check to see if the “Pressed_FW” action is being pressed. At the same time we also check to see if the “Pressed_BW” action is not being pressed. We need to be specific about this. If the player if pressing the forward action, then the player will move forward.
Otherwise, if the player is not pressing “Pressed_FW” and is pressing “Pressed_BW”, we make the player move backwards.
If that’s not the case, we then check to see if both the forward and backward actions are being pressed. If they are, we don’t do any movement at all. This isn’t that important but I like it as I find it to be a cleaner way of doing things. And if the player is accidentally pressing both he won’t fall off the edge of a platform or cliff eh may be on.
The last two statements are for when nothing is being pressed. The first “elif” statement checks if neither the forward or backwards keys are being pressed. If not then there is no velocity added in the forward or backward direction.
The very last “else” statement is there in case that, for whatever reason, none of the “if” statements before it don’t work. I have no idea how that ma happen but you never know what could happen in the future. And it cost a microscopic amount of memory so I keep it in there.
Now that we’ve got the overview of how the “if” statements work let’s look at all the code:
# FORWARDS #
if(Pressed_FW and not Pressed_BW):
TempMoveVel_FWAndBW.x = -DirectionInNormalVec3_FWAndBW.x
TempMoveVel_FWAndBW.z = -DirectionInNormalVec3_FWAndBW.z
# BACKWARDS #
elif(not Pressed_FW and Pressed_BW):
TempMoveVel_FWAndBW.x = DirectionInNormalVec3_FWAndBW.x
TempMoveVel_FWAndBW.z = DirectionInNormalVec3_FWAndBW.z
# BOTH #
elif(Pressed_FW and Pressed_BW):
TempMoveVel_FWAndBW.x = 0
TempMoveVel_FWAndBW.z = 0
# NEITHER #
elif(not Pressed_FW and not Pressed_BW):
TempMoveVel_FWAndBW.x = 0
TempMoveVel_FWAndBW.z = 0
# FAILSAFE #
else:
TempMoveVel_FWAndBW.x = 0
TempMoveVel_FWAndBW.z = 0
When the player presses the forward or backwards action the direction the player is looking is taken from “DirectionInNormalVec3_FWAndBW” and put into “TempMoveVel_FWAndBW”. Note that we only take the X and Z axis of that normal. The Y axis velocity will be taken care of in different sections, like the falling or jumping section.
Also see that when the player presses the backwards action it simply does the opposite of what pressing the forward action does. Which in this case means that it’s positive. I’ve already explained this before in the “_physics_process() > Get Info” section. But a shortened version of the explanation would be that when I made the character I pointed him in the wrong direction on the Z axis. This doesn’t cause any physics problems, it only means that I have to make the forward/backward movement velocity the opposite of whatever it would be if I had faced the player character in the +Z direction in the “Player.tscn” file.
# LEFT #
if(Pressed_LEFT and not Pressed_RIGHT):
TempMoveVel_LeftAndRight.x = -DirectionInNormalVec3_LeftAndRight.x
TempMoveVel_LeftAndRight.z = -DirectionInNormalVec3_LeftAndRight.z
# RIGHT #
elif(not Pressed_LEFT and Pressed_RIGHT):
TempMoveVel_LeftAndRight.x = DirectionInNormalVec3_LeftAndRight.x
TempMoveVel_LeftAndRight.z = DirectionInNormalVec3_LeftAndRight.z
# BOTH #
elif(Pressed_LEFT and Pressed_RIGHT):
TempMoveVel_LeftAndRight.x = 0
TempMoveVel_LeftAndRight.z = 0
# NEITHER #
elif(not Pressed_LEFT and not Pressed_RIGHT):
TempMoveVel_LeftAndRight.x = 0
TempMoveVel_LeftAndRight.z = 0
# FAILSAFE #
#This is a failsafe case.
else:
TempMoveVel_LeftAndRight.x = 0
TempMoveVel_LeftAndRight.z = 0
Here it’s the same as before, but we are doing the left and right movement of the character.
So if the player presses left, the X axis velocity of “TempMoveVel_LeftAndRight” will equal the negated value of the X axis of “DirectionInNormalVec3_LeftAndRight”. And the Z axis will equal the negated value of the Z axis of “DirectionInNormalVec3_LeftAndRight”.
Let’s look at how that would be visualized:
This is the same picture I used in the “_physics_process() > Get Info" section of this document.
Let’s say that that the X normal in this represents “DirectionInNormalVec3_LeftAndRight” and that it equals “(-0.914192 , 0.0 , 0.405281)”. So when the player presses left, we want to take the negative value of the X axis of that vector and negate it. So that means it would look like this:
So now that we’ve negated the X axis, we will do the same now for the Z axis, which will look like this:
And as you can see the X normal is now on the left side of the player. And that’s how we get the global direction to the left of the player.
So that means in the next “elif” statement that looks like this:
elif(not Pressed_LEFT and Pressed_RIGHT):
TempMoveVel_LeftAndRight.x = DirectionInNormalVec3_LeftAndRight.x
TempMoveVel_LeftAndRight.z = DirectionInNormalVec3_LeftAndRight.z
Since “DirectionInNormalVec3_LeftAndRight” is already where we want it we just use it as-is.
So now that we have the velocity directions of the player, we now need to add them together and multiply it by the final walk velocity that we want. The code looks like this:
FinalMoveVel =
(TempMoveVel_FWAndBW + TempMoveVel_LeftAndRight).normalized() *
FinalWalkVelocity
So what we are calculating is the final movement velocity. So first thing we do is add the forward/backward velocity vector with the left/right velocity vector.
Let’s say that the player presses both the forward and left movement keys. And let’s say that there are the vectors:
TempMoveVel_FWAndBW = ( 0.914192 , -0.405281 )
TempMoveVel_LeftAndRight = ( -0.405281 , -0.914192 )
The vectors might look like this:
So if we add them together:
We get this:
(0.914192 , -0.405281) + (-0.405281 , -0.914192) = (0.508911, -1.319473)
Which looks like this:
Now, the only problem is that this resultant (the word for the sum of two vectors) is too long. Its current length is about “1.41”. This is called the "crest factor". It's not a big deal to know what it is but basically if the character was looking 45 degrees to the right, the players normal on the X and Z axis would be about "( 0.7071 , 0.7071 )", and if you add those together it equals about "1.41".
So now let’s go to the next step in the line of code: the normalization.
(TempMoveVel_FWAndBW + TempMoveVel_LeftAndRight).normalized()
( 0.508911 , -1.319473 ).normalized() = ( 0.359854 , -0.933008 )
Then after that we multiply the final movement vector by the movement velocity. Let’s say that the final movement velocity is “10”:
( 0.359854 , -0.933008 ) * FinalWalkVelocity
( 0.359854 , -0.933008 ) * 10 = (3.598545, -9.330085)
This final result means that if the player were to keep going in this direction for 1 second the position of the player after that 1 second would be “(3.598545, -9.330085)”. Remember that in “move_and_slide()” the movement vector (the first argument) doesn’t need to be multiplied by “delta” as that is taken care of in the function.
Now we are going to be calculating the vertical velocity. There is much more going on here than in the horizontal velocity section as we have many things to check and there are three states of vertical movement: falling, jumping, and on the floor.
The first part we are going to look at is the “On Floor” section. The first lines are:
if(State_OnFloor and State_Falling and not State_Jumping):
Falling_IsFalling = false
So this first “if” statement looks to see if the player is:
- On the floor.
- Falling.
- Not jumping.
If these things are the case then we simply state that the player is no longer falling. This “if” statement is basically for when the player hits the floor after falling. When that happens we say that the player has stopped falling.
elif(not State_OnFloor and not State_Jumping):
State_Falling = true
If the first “if” statement doesn’t go through then we go on to this one which checks to see if the player is:
- Not on the floor.
- Not jumping.
If these statements are correct then the player must be falling. So we set the state as such.
elif(State_OnFloor and State_Jumping and not State_Falling):
State_Jumping = false
Jump_CurrentVel = 0
State_Falling = true
Falling_Speed = 0
Falling_CurrentTime = 0
If the last couple of statements aren’t true, then we check to see if:
- The player is on the floor.
- The player is jumping.
- The player is not falling.
If these statements are correct then the player may have landed on a platform while jumping onto it, but without falling onto it. Confusing? This is just in case the player doesn’t actually fall onto a platform or floor, but in stead jumps onto it from the side and just happens to touch the floor while going up. This case is for the off chance that might happen.
So that case tree took care of what happens when the player hits the floor. Now we move on to when the jump action is pressed.
Now we’re in the “JUMP PRESSED” section.
if(Pressed_Jump and Jump_Released):
First thing we check is not only if the jump action was pressed, but we also check to see if the jump action was previously released. This is so the player can’t keep holding down the jump action and keep jumping.
if(State_OnFloor):
State_Jumping = true
State_Falling = false
Jump_CurrentTime = 0
Now we check to see if the player is on the floor. If he is then we:
- Set the “jumping” state to true.
- Set the “falling” state to false.
- And reset the jump timer to 0, which is the beginning of the jump.
In this section we take care of the actual jumping of the player, instead of just checking and setting states.
if(State_Jumping and not State_Falling):
if(State_OnFloor):
if(Jump_Vel <= Falling_Gravity):
State_Jumping = false
Jump_CurrentVel = 0
State_Falling = true
Falling_Speed = 0
Falling_CurrentTime = 0
First we check if the player is in the “jumping” state and not falling. If these are correct, then we check if the player is on the floor. If so, then we check to see if the jump velocity is can even pull the player up against the pull of gravity.
If the jump velocity isn’t enough to pull the player up, then what we basically do is cancel the jump by setting all the appropriate states and variables. This is another “just in case” statement.
To cancel the jump we:
- Set the “jumping” state to false.
- Set the jump velocity to 0.
- Set the “falling” state to true.
- Set the falling speed to 0.
- The falling speed will be modified later in the script, in the “FALLING” section, allowing the player to still be affected by gravity when on the floor.
- Reset the falling timer.
elif(Jump_Released):
Jump_CurrentVel = Jump_Vel
FinalMoveVel.y = Jump_CurrentVel + -Falling_Gravity
This “elif” statement occurs if the jump velocity is enough to pull the player up against gravity and the jump action has been previously released.
If it has it sets the initial applied jump velocity to the “Jump_CurrentVel” variable, which is the velocity that will actually be applied to the player. It is attenuated as the player reaches the peak of his jump.
After that we set the final movement velocity to be negated gravity plus the current jump velocity. “Falling_Gravity” is negated because it’s a positive number. This is because it represents the power of gravity, not necessarily the direction. So, in theory, if you wanted the player to be pulled up instead of down, we would just invert the applied gravity.
elif(not State_OnFloor):
if(Jump_CurrentTime < Jump_Length):
So if none of the previous statements are true, then we check to see if the player is not on the floor. If not, in the first “if” statement we check to see if the jump timer has reached the end of the jump length. If the jump isn’t over yet we move on to:
if(State_OnCeiling):
Jump_CurrentVel = 0
Falling_Speed = 0
Falling_CurrentTime = 0
State_Jumping=false
Falling_IsFalling = true
State_Falling=true
else:
Jump_CurrentVel =
Jump_Vel - (Jump_Vel * ( pow( Jump_CurrentTime / Jump_Length , 2.0 )))
Jump_CurrentTime += delta
FinalMoveVel.y = Jump_CurrentVel + -Falling_Gravity
If the player hits the ceiling with his head while in the middle of a jump, we cancel out the jump like we would in the previous section if the jump velocity isn’t enough to get the player off the ground.
Otherwise, if the player isn’t hitting the ceiling, we calculate the current jump velocity, advance the jump timer, and apply the final vertical velocity.
Let’s look at an example of how we calculate the jump velocity. We calculate the jump velocity as such:
# Hypothetical values.
Jump_Vel = 15.19
Jump_CurrentTime = 0.5
Jump_Length = 1.0
# The line of code. #
Jump_CurrentVel =
Jump_Vel - (Jump_Vel * ( pow( Jump_CurrentTime / Jump_Length , 2.0 )))
# In real numbers. #
Jump_CurrentVel =
# First we put in the real numbers.
15.19 – (15.19 * ( pow( 0.5 / 1.0 , 2.0 ) ) )
# Then we calculate the first argument of the “pow()” function.
15.19 – (15.19 * ( pow( 0.5 , 2.0 ) ) )
# Then we calculate the “pow()” function.
15.19 – (15.19 * 0.25 )
# Lastly we calculate what’s in the parentheses and subtract that from “15.19”.
15.19 - ( 3.7975) = 11.3925
Let’s walk through this, even though we already have in the above example.
15.19 – (15.19 * ( pow( 0.5 / 1.0 , 2.0 ) ) )
^^^
( 0.5 / 1.0 ) = 0.5
^^^
15.19 – (15.19 * ( pow( 0.5 , 2.0 ) ) )
First we calculate how long the jump has been going on in comparison to how long the jump should be. That’s what the “0.5 / 1.0” is. It represents “Jump_CurrentTime / Jump_Length”, which gets us the percentage of how far we are into the jump.
15.19 – (15.19 * ( pow( 0.5 , 2.0 ) ) )
^^^^
pow( 0.5 , 2.0 ) = 0.25
15.19 – (15.19 * 0.25 )
Then we calculate the “pow()” function which gives us “0.25”. And of course we use a “pow()” function here so that the jump velocity isn’t too linear. Try making the jump linear to see how it looks, if you’re curious.
15.19 – (15.19 * 0.25 )
15.19 – 3.7975 = 11.3925
And then we just do the rest of the line and we have our current jump velocity.
And again, after we get the jump velocity, we just add the current delta to the jump timer and put the current jump velocity into the final movement velocity vector that is used in “move_and_slide()”.
Now let’s move on to the next case that executes if the jump is over:
# Otherwise, if the jump is over...
else:
Jump_CurrentVel = 0
Falling_Speed = 0
Falling_CurrentTime = 0
State_Jumping=false
Falling_IsFalling = true
State_Falling=true
If the jump is over we cancel out the jump as we have previously when the jump velocity isn’t enough to pull the player off the ground and the player hits a ceiling.
Here we take care of when the player is falling. Note that if the player is on the ground and isn’t literally falling, he is still considered to be “falling”, as he is not jumping. When the player is on the ground the vertical velocity of the player is “-(Falling_Gravity * Falling_Speed_Multiplier_Default)”, which by default would be “-(9.8 * 0.25) = -2.45”. So that means whenever the player is standing still or walking on a perfectly level floor he is being pulled down at 2.45 m/s. If the player was to be pulled down anymore than that he would slide down on slopes when standing still.
On to the code:
if(State_Falling and not State_Jumping):
First we check if the player is falling and not jumping.
if(not State_OnFloor):
if(Falling_Speed < Falling_TerminalVel):
Then we check to see if the player is not on the floor. If he isn’t then we go on to check if the current falling speed of the player is less then terminal velocity. If it is we go on to this code, here:
if(not Falling_IsFalling):
Falling_IsFalling = true
Falling_CurrentTime = 0
elif(Falling_IsFalling and Falling_CurrentTime < Falling_TimeToHitTerminalVelSec):
Falling_CurrentTime += delta
Falling_Speed = Falling_TerminalVel *
pow(Falling_CurrentTime / Falling_TimeToHitTerminalVelSec, 0.4)
if(Falling_CurrentTime >= Falling_TimeToHitTerminalVelSec):
Falling_CurrentTime = Falling_TimeToHitTerminalVelSec
else:
Falling_CurrentTime = Falling_TimeToHitTerminalVelSec
Falling_Speed = Falling_TerminalVel *
pow(Falling_CurrentTime / Falling_TimeToHitTerminalVelSec, 0.4)
The first “if” statement checks to see if the player hasn’t started falling yet. This happens when the “State_Falling” bool is first switched to true.
If the player just started falling then we set the “Falling_IsFalling” bool to true to say that the player has started falling, and then we reset the falling counter to 0 to signify the start of the falling process.
Otherwise, in the second statement, which is an “elif” statement, we check two things:
- Has the player started falling?
- Is the current falling time less then the time it takes to hit terminal velocity?
If these are both true then we advance the falling timer. In that same “elif” statement we check to see if the falling timer is past its limit. If it is we set it back to its limit so as to not go over by a tiny amount, which can cause a little “jump” when the player hits terminal velocity as the velocity goes above terminal velocity and then back down quickly in the next loop.
The last statement, which is an “else” statement, executes when the falling timer has passed its limit. If that happens then we set it back down to its limit.
After these statements, we finally calculate the falling speed, using a “pow()” function to have a non-linear speed increase.
# Otherwise, if the player is on the floor...
else:
# If the player is moving...
if(FinalMoveVel.x != 0 or FinalMoveVel.z != 0):
Slope_AffectSpeed()
else:
Falling_Speed_Multiplier = Falling_Speed_Multiplier_Default
Falling_Speed = Falling_Gravity * Falling_Speed_Multiplier
So, what happens if the player is on the floor?
First we check to see if the player is moving. If he is then we execute “Slope_AffectSpeed()”
Otherwise, if the player is not moving we set “Falling_Speed_Multiplier” to “Falling_Speed_Multiplier_Default”, which is “0.25” by default.
After that we calculate the final falling speed using that multiplier.
FinalMoveVel.y = -Falling_Speed
After all these “if” and “elif” statements we apply the falling speed to the Y axis of the final movement velocity, making sure to negate it so that the player falls down and not up.
Now we’re getting to the end of the script. Here we actually start applying all the calculation we have done up to this point.
Here we rotate the player and his camera. Check the “_unhandled_input()” section for more information.
if(Mouse_Moved):
self.rotate_y(deg2rad(-Mouse_Rel_Movement.x) * Cam_RotateSens)
Node_Camera3D.rotate_x( deg2rad(-Final_Cam_Rot_Local_X) *
Cam_RotateSens)
Mouse_Moved = false
First we check if the mouse was even moved this frame. If it was we rotate the Y axis of the whole player according to the relative mouse movement on the X axis. Let’s look at this line more closely:
self.rotate_y(deg2rad(-Mouse_Rel_Movement.x) * Cam_RotateSens)
So first we have “self.rotate_y()”. The “self” referred to here is the node that the script is attached to. And “rotate_y()” is a function of “Spatial”, one the ancestors of “KinematicBody”. There’s no need to worry about physics when rotating the character on the Y axis because the collision shape of the player is a capsule. So if the player rotates he won’t hit anything accidentally like he might if he had a cube collision shape:
At any rate we rotate the player on the Y (vertical) axis. We convert the X axis of “Mouse_Rel_Movement” to radians so that the mouse movement has more accuracy.
Let me explain. “rotate_y()” takes an angle in radians as its only argument. If the mouse moved only 1 pixel on the screen, then after being multiplied by “Cam_RotateSens” it would equal “0.25” radians. That means that the player would rotate 0.25 radians (14.3 degrees) when the player only moved the mouse one pixel. That’s way too much.
So instead we make the relative motion of the mouse represent degrees. So if the player moved the mouse only 1 pixel, after being multiplied by “Cam_RotateSens” it would equal “0.25” degrees, instead of radians, therefore having greater accuracy.
We could change all of this by just multiplying the relative mouse movement by a smaller number, but I’ve already written the script. And besides that I don’t think it would have any advantage to performance and I like to try to keep the “Cam_RotateSens” below 1.0 but not too low.
After we rotate the whole player, now we need to rotate the camera up and down. We do this the same way we rotate the whole player, but when we specify the amount to rotate the camera we have to negate it as moving the cursor down provides a positive number, and vice versa.
Then we set “Mouse_Moved” to false to say that the mouse movement has been taken care of and to not rotate the character anymore until a new mouse movement event occurs.
Here we finally apply the linear velocities that we’ve calculated in the previous sections.
if(State_OnFloor):
FinalMoveVel += get_floor_velocity() * delta
move_and_slide(FinalMoveVel, FloorNormal, SlopeStopMinVel, MaxSlides,
MaxFloorAngleRad)
The first thing we do is find out if the player is standing on something. If he is we add the floor’s velocity to the player. Otherwise, if the player is not on the floor this calculation never takes place.
After that we apply the final movement velocity vector with all the previous calculations. And that’s it.
After we move the player, we still have some things left to do. First thing is we need to get the new states of the player, as they’re updated whenever “move_and_slide()” is executed.
State_OnFloor = is_on_floor()
State_OnWalls = is_on_wall()
State_OnCeiling = is_on_ceiling()
SlideCount = get_slide_count()
Player_Position = translation
All we do here is execute “Step_PlayerUp()". Click the link to find out more about this function.
Step_Player_Up()
Here we check the progress of any camera interpolation that is taking place and execute “InterpolateCamera()”. Click the link more for information.
if(CamInterpo_DoInterpolation == true):
CamInterpo_CurrentTime_Secs = InterpolateCamera(
CamInterpo_StartingPos_Local_Y ,
CamInterpo_CurrentTime_Secs ,
delta
)
So first we check if the “CamInterpo_DoInterpolation” bool is true to find out if we need to do it. If it we execute “InterpolateCamera()”, making sure to have it give the return value to “CamInterpo_CurrentTime_Secs” so that we know how long the interpolation has been going on.
All we do here is execute “Touch_CheckAndExecute()”.
Touch_CheckAndExecute()
The last section.
Debug_Label_String = "FPS: " + str(Engine.get_frames_per_second())
Debug_Label.set_text(Debug_Label_String)
We set the debug label string as the FPS. Then we set the label according to the debug label string variable. This section isn’t necessary but can be useful.