Now that we know how to move and animate a
character in Unity, we want to learn how to jump! Let’s do it! Today we are going to implement a
jump mechanic using Unity’s Built-In Character Controller, animations and the new input system!
But jumping can be hard to get just right! So we’ll do so trying to replicate the mechanic from
the king of jumping in video games: Super Mario! My name is Nicky and this channel is
iHeartGameDev, a channel all about game development. The biggest shout
out to the backers over on Patreon for not only their support but for also helping
to choose the topic! Now let’s get started! What can be said about Mario’s
Jump? Well, quite a bit, actually! Outside of Mario’s jump “feeling” perfect during
gameplay, it consistently reaches the same height every single time as long as the jump button
is held down. We can also see that once Mario reaches the apex, or max height, of the jump,
he begins to fall faster! And we know that if we let go of the jump button, it will shorten
the duration and height reached for the jump. By slowing down the playback, we can see that
the animation transition between Mario’s idle, walking and running states to his jumping states
is instantaneous. And, if we jump consecutively, Mario performs alternate jump types; each
with its own animations and trajectories. And, We have a checklist of what can make
for a great jump! Let’s go through and try to replicate each component
that we just defined to make our own jump! By the end of this video, we’ll
have a jump that looks just like this! Opening Unity, let’s recap the previous episode of
this series to make sure we’re all starting from the same place and have the same fundamental
understanding of what we are working with. We have downloaded the Jammo character model
featured on the hit youtube channel: Mix And Jam. We also went ahead and both downloaded and
retargeted animations from Adobe’s Mixamo platform for Jammo to use. Jammo has an animator component
to control the transitions between animation states, and a character controller component
that we use for movement and collision detection. We’ve used the new input system
to create an input actions asset, and within that we’ve set up an “action map” and
“actions” that currently track the player’s input for both movement and running. And to actually
use all of that, we’ve programmed the movement, animations, rotation and gravity for Jammo!
Be sure to check out the full tutorial to get the complete walkthrough or you can grab
access to that project file and this episode’s complete project file over on Patreon while also
supporting the channel. Otherwise, you can sit back and enjoy the video by just watching to
see how we implement this version of a jump! Taking a look at our PlayerInput input action
that we created to use the new input system, we’ll see the Character Controls action map.
Here, we currently have the “Movement” and “Run” Actions which are how we currently
give players control of Jammo. Let’s start by creating a new Action called “Jump”
which we’ll keep as the “Button” action type. In our code, “Buttons” will return boolean true
or false values depending on if the player is pressing the button or not, so we’ll be able to
use that when programming the jump logic! Next, we’ll need to map this to a couple of buttons!
Pressing on the plus button next to the action, we will see the option to add a new binding.
When we select this newly generated binding, we can use the binding property to select
which button we want our jump to be set to. And using the “Listen” option, we can just press
the spacebar for the option to be selectable. We can also add another binding, except
for the “A” button, or “south” button, on a controller by following that same process. Great! Let’s open up our code and begin
to program our jump implementation. Based on the jump action that we just set up
in the editor, let’s program the jump callback function that will keep track of whether or not
either of the assigned jump buttons are pressed. We can do so by first creating a variable that we
will use to store the jump button’s current value. We’ll write “the boolean
isJumpPressed is equal to false” because by default, the player
will not be pressing this button. Next, we’ll write two callback functions
that will keep our “isJumpPressed” variable updated to the proper value. As a reminder,
callbacks functions are executed when certain events take place and provide “context” or “data”
for us to use from the event. So we’ll want the callback function to be triggered when the
button is pressed and when it is released! Now, in the previous episode we also set up
callbacks as can be seen here. To properly update our code with the player’s input, we set
the callbacks to be invoked during the “started”, “performed” and “canceled” phases of the
player’s input. What this means is that when a button is first pressed, the “started”
phase is triggered and our callback is run. The “Performed” phase is typically triggered when
a player input has been completed or updated, like an analog stick moving from left to right.
And the “canceled” phase is when the player input has stopped that form of input, like releasing
a button. For our jump callback, we’ll only need to know when it’s initially pressed and when it’s
released so we can use “started” and “canceled”. We’ll write “PlayerInput dot CharacterControls
dot jump dot started” which will map into our PlayerInput input action, into
the CharacterControls action map, select the jump action and designate that
we want to listen for the “started” phase. We’ll then add “plus equals” passing in the
callback function that we want triggered. We will name that callback “onJump”
and we can define that function below. But first we will duplicate this code
and replace “started” with “canceled” Now, “onJump” will not return any value
from the function, so we can write “void onJump which takes context of type
InputAction.CallbackContext” as an argument. As a reminder InputAction.CallbackContext
is only available if we import the namespace “UnityEngine dot InputSystem”,
and we have access to the PlayerInput only because that’s what we titled the input
action asset, selected to generate a c# class and created the instance of it within the code.
This was all fully explained last episode. Within the callback we’ll set the “isJumpPressed”
value equal to context dot readValueAsButton. And just like that, we’ll have access to whether
the jump button is pressed from within our code! We can test this by also logging the
value to the console just to be sure! Awesome! Now we can actually focus on learning
how our jump will work. As a disclaimer, I am not an expert in physics, so this is going to
be my interpretation of some excellent resources that I found when researching for this video
-- the links will be in the description! Let’s start by visualizing a character jumping.
How do we actually get them into the air? Well, physics would say that we apply a positive force! At the start of a jump, the velocity y value
of the character will immediately bump to a relatively high positive number one time. We can
consider this explosive force the “initial Jump Velocity”. This point in time will actually be
the highest that the velocity y value will reach because gravity is the constant negative acceleration that is continuously going
to be added to the velocity y value. What this means is that over time, the character’s
upward velocity will slow, the character will reach the apex, or highest point, of the jump,
and then the velocity y value will proceed back to a negative number and bring the character
back down to the ground. And that is a jump! Now, what can we take from this understanding of
jumping? If we graph out the velocity over time, it would be represented by a descending
line because our acceleration, gravity, is a constant negative number being added to
the velocity, and as just mentioned the initial starting point of the jump is the highest the
velocity y value will be. If we were to track the character’s position from a standstill, because
gravity is a constant number, we’ll find that the trajectory maps out to be a symmetric curve
known as a “parabola”. Knowing that the arch of the jump is a Parabola is incredibly helpful/!
The properties of a Parabola will enable us to actually find the values of variables we’ll
need to make our character jump in our code. So what are the variables we will
need for our jump implementation? We’ll use “Gravity” as the constant negative
acceleration, “initialJumpVelocity” that propels our character into the air, “maxJumpHeight”
which is the maximum height we would want our character to reach if the jump button
is held down the entire time of the jump, and “maxJumpTime” which is the longest
length of time for the jump possible from a standstill until it reaches the same
y position that it started at. And we’ll also add “isJumping” which will be true or false
depending on if the player is actively jumping. Let’s go ahead and declare variables for
each of these in the scope of the class. Each will be of type float except
isJumping will be a boolean. And, actually, gravity is something
we set up in the previous episode, as we’ll notice both the gravity variable
and another called groundedGravity. This additional groundedGravity variable
is used when the character is grounded, or touching the ground. When grounded, we switch
to this smaller value, making it less likely for the character controller to clip through the
floor from too high of a downward velocity. We also have a function called handleGravity
that currently performs this logic, switching between the two. But let’s take a second
to move it to the bottom of the Update function. “handleGravity” is called inside of the Update
function which means it’s called once per frame. We’ll also notice the characterController’s “Move” function is being called inside
the Update function as well. The character controller’s isGrounded property
is determined by if character was touching the ground in the last move call, which is
why we move it below where move is called. Getting back to the variables we just
declared and our new knowledge of jumping, let’s take a look at the formulas we can use to
get our desired jump implementation. Let’s create a new function titled “setupJumpVariables”
which we will call inside of Awake. Now, with Parabolas, we can calculate gravity
using the following formula: “minus two times the max height of the jump over the time to the
apex of the jump, squared”. And because we know our jump parabolas are symmetrical, we know that
the time to the apex of the jump is one half times the max jump time. Let’s declare a new variable,
timeToApex, which cuts the “maxJumpTime” in half. But we’ll need to know how long, in seconds, we
want this jump to take for the “maxJumpTime.” Let’s set a default of half a second,
making the TimeToApex a quarter of a second. We can, of course, modify this value later to speed up or slow down the jump based
on the result and our preferences. And we’ll also need to establish the maxJumpHeight
value for the top half of this equation. Let’s set this variable to a default value of
1. Again, we can modify this if the jump isn’t high enough or it’s too high. Now, let’s
set the gravity variable to our equation. Ok! We have gravity, time and jump height covered.
Of our variables we need for this jump, we just need to calculate one more: initialJumpVelocity.
We can calculate this using the equation: “two times the maxJumpHeight over time to the apex”.
And lucky for us, we already have those values! Awesome! We have what we need to jump! And our
variables are calculated properly through science! if you want to understand these equations a
little more. Be sure to check out the GDC talk Math for Game Programmers: Building a
Better Jump presented by Kyle Pittman of Minor Key Games and Gearbox. Kyle
does an amazing job elaborating and explaining why this is such
a solid approach to jumping. We are ready to jump! Let’s write the
first iteration of our jump function: “handleJump”. Here we will check to see if
“isJumping” is false, the characterController’s “isGrounded” property is true and that
the “isJumpPressed” boolean is also true. If these conditions are met, we
want to set isJumping to true and add our initialJumpVelocity to our velocity!
Remembering from the last episode that we have the two variables that store our possible velocities:
currentMovement and currentRunMovement, we will want to add this initialJumpVelocity to the y
value of both! And because our handleGravity function continuously applies gravity to
both currentMovement and currentRunMovement, we can count on both velocities always
having the same velocity y value. Now let’s invoke handleJump at the bottom of
our update function and enter play mode to see our current results! Success! Our character
jumps into the air and falls back down! However, we will notice that we can only jump one
time. That’s because one of our conditions in the handleJump function is not being met: “isJumping”
is always set to true after the first call is made but never set back to false! In our handle
jump function, which is invoked every frame, we can add an “else if” conditional statement that
checks if the isJumpPressed variable is false, the isJumping variable is set to true, the
characterController’s isGrounded property is true. If that’s the case, then we should
set isJumping back to false! Testing again, we can now see that we
are able to jump multiple times! Amazing! There are a couple of things that we should
be clear about. The isGrounded property can be quite finicky in my personal experience.
If the character is touching the ground, but there is no downward velocity,
the isGrounded property will be false. Additionally, it’s about time we talk about
our current implementation of Velocity. At this point, we can look at where we set
the currentMovement and currentRunMovement x and y values. These values do not accelerate
or decelerate in the current implementation. Instead they are set directly to constant numbers.
So they are fine as is. But with our gravity, we are adding that negative value continuously
over time, meaning acceleration (or deceleration because it’s a negative value)!
With our current implementation, we are simply taking the currentMovement and
currentRunMovement velocity, adding gravity, or acceleration, and modifying the position in the
CharacterController’s move function. This is known as “Euler integration” and is quite simply the
easiest implementation that gets the job done. However, Euler integration has a main drawback: trajectories are inconsistent
depending on the framerate! Now we are talking about very minor differences,
but they can be the difference between a player landing or missing a tough jump, and that can be
incredibly frustrating from a player’s standpoint, and it’s unnecessary because we can fix
the issue quite easily with alternatives. For today, we’ll use Velocity Verlet integration
to fix the issue. With Velocity Verlet, instead of solely adding the acceleration to
velocity when calculating the new position: instead we store the previous velocity,
calculate the new velocity in the same manner as Euler integration, and then we
add the old and new velocities together, multiply by 0.5 to average them and finally
multiply by the time step, which is delta time. So the only place that we need to do this with
the current implementation is in our handleGravity function when we are applying gravity to both the
currentMovement and currentRunMovement variables. We’ll create a “previousYVelocity” variable
variable and set it to their current y velocity value. Then we’ll create another
variable titled “newYVelocity”, which will be equal to the old one plus acceleration, which
in this case is gravity, times time.deltaTime. And with one final variable titled nextVelocity,
we’ll add them together, multiply by .5 to average them and we’ll set the currentMovement and
currentRunMovement y values to this new value! We’ll notice that we skipped the multiplication
by Time.deltaTime here, but that’s only because we multiply the entire currentMovement velocity
variable in the Controller.Move method. One more change we need before we test
is back in our handleJump function. Now we can use the same velocity verlet logic to
find the average of the previous velocity and our initial jump velocity value. However, that gets
a little messy with our grounded gravity value. Instead, for today, we are going
to just multiply our initial Jump Velocity by .5 which would assume
that the previous y velocity was 0. And just like that, we have
a framerate independent jump that will always reach the same height. Looking back at our checklist of building an
awesome jump, the next step is to modify our jump trajectory. As mentioned, the math that we’ve
used to create our jump stems from Parabolas, or perfectly symmetric arches, but part
of what makes Mario’s jump so great is that it doesn’t feel particularly “floaty”
and we can control the jump’s length. Let’s modify our jump so that it no longer is
symmetrical and feels a little weightier! Looking at our graph of position over time, we
know that up until the apex, we are considered jumping and our character is rising. But once we
meet the peak height of the jump, the character starts descending or falling! This is the point
where we want to start to increase gravity! Let’s create a new variable called “isFalling”.
Because we know that at the apex, the velocity y value is 0, we know that from that point
on, this “isFalling” variable should be true! If the character isFalling, we’ll
want to increase the gravity. Let’s create another variable titled
“fallMultiplier” and set it to a value of 2! This will be used to speed up the fall! The
larger this multiplier’s value, the steeper the second half of this graph will be and the
faster the character will return to the ground! To apply this multiplier, we can create an
additional conditional statement checking if we are falling, and then multiply the gravity being
added to our velocity variables by the multiplier. Ok! it’s a little too quick and falls a little
too short. Let’s add another quarter of a second and raise the height to 4. Awesome -- our
jump is really starting to feel great to use! And it would be even better if we could shorten
its length if the player releases the jump button. Fortunately, this is fairly straightforward
after the changes we just made for falling. The fall multiplier is waiting to be used
once the conditions are met. So for isFalling, we’ll also check if the isJumpPressed variable is
false. Now, the fall multiplier will be applied the moment the jump button is released and
the character will fall back down faster! If we want, we can also apply a clamp to
the velocity y value so that it caps out at a specific maxSpeed. If the character
falls from a tremendous height in a game, this would be helpful in preventing
the character from falling down at an insanely high speed. But
this is completely optional. Ok! We’ll definitely have noticed that
the character’s animation is stuck in the same animation state as the movement
state Jammo was currently in: be it idle, walk or run. We are going to want to switch to
some jump animation when the jump takes place! If you are new to animation, be sure to check out
my tutorial “how to animate characters in Unity3D: animator explained” for a better
understanding of what’s to come next. Taking a look at the animator component that’s
currently attached to Jammo, we’ll see the animation controller. Double clicking on this
will open the animator window and display our 3 different animation states that we currently
have: idle, walk and run. Our character will be able to jump in all three of these states! Let’s
create a new animation state by right clicking, navigating to “create state” and selecting
“empty”. Then we’ll retitle this state “Jump” We need a jump animation. While in the future,
we’re going learn how we can create our own animations in the 3D modeling software, Blender:
Today, we’ll head over to Adobe’s Mixamo.com and find a few animations for us to use! We’ll select
the Y Bot from the character’s tab, and then search for the following three animations:
Female Dynamic Pose, Male Dynamic Pose, and Stylish Flip! We’ll use the default settings
while setting the Format to FBX for Unity. Dragging these three animations into
our animations folder, we’ll select all three and find the Rig tab in the inspector.
Here we’ll select the “animation type” and switch to humanoid, then press apply. Our jammo
character is currently set to humanoid, meaning we can retarget these
animations to work with Jammo! To be sure these animations were all imported
correctly, we’ll select each individually, and press on the “animation” tab. Here we
can verify that they are properly named. And for the stylish flip, we are actually
going to want to modify the animation settings. We’ll select “Bake Into Pose” for the
Root Transform rotation and y Setting to remove the root movement from the animation.
Switch to “Original” for the Based Upon setting for root transform rotation and center of mass
for root transform y and root transform position. And we’ll set the Root Transform Y Offset to
a value of -1 to slightly move the character higher in the animation. Lastly, we are
going to want to modify the keyframes of this animation because there are too many
unnecessary parts that just don’t blend well! We’ll want to move the beginning frame to
the pose that looks closest to the a regular standing position frame 6, and the last frame to
the moment right before impact with the ground, frame 40. Pressing apply and this animation
is ready for use later in the tutorial! Let’s first get one of these animations working
with the jump. Back in the animator, we can select our newly created “Jump” state. And in the
inspector, we can find the “Motion” property. Using the circle selector, we can
find the “Female Dynamic Pose”! And we’ll begin making transitions to and from
the animation state with all three of the other movement states. We’ll be sure to disable “Has
Exit Time” on each of these transitions so that the animations don’t need to finish playing
to begin the transition to the next animation. And we’ll need to create a new
boolean animator parameter: “isJumping”. For each transition to the
jump, we’ll want “isJumping” to be true. And for the transitions back, not only
will we want isJumping to be false, but we’ll need to also validate that the other
parameters match with the corresponding animation state. So back to idle, we’ll need isRunning
and isWalking to be false. Back to walking, isWalking to be true and isRunning to be false.
And for running, both will need to be true. Entering play mode, we’ll jump and nothing
will happen. This is because we will need to set this animator isJumping parameter
to true and false from within our code. We will set it to true within the
handleJump function because that’s when we are guaranteed to start jumping. And false in
our handleGravity function when we are grounded. Because we already have the reference to the
animator component from the previous tutorial: we can set the isJumping boolean value
using animator dot set bool passing in the string isJumping and the
proper true or false value. However, we’ll create two new variables in
the scope of the class: an integer titled “isJumpingHash” and a boolean “isJumpAnimating”.
Within our awake function we’ll set isJumpingHash to Animator dot StringToHash passing in
“isJumping” as the argument. And now we can replace the string version of “isJumping” with
isJumpingHash inside of the SetBool functions. And only set isJumping to false if it’s
animating. This offers a slight performance boost! Testing in play mode and we will now
see our character switch to jumping when the jump button is pressed! We did it! We can make slight modifications to the
transitions to and from the jump state so that they’re more instantaneous like Mario’s.
We do this by reducing the transition duration. Selecting the transition and opening up
the “settings” dropdown, let’s switch the transitionDuration property to .1. This will make
the dynamic animations created from interpolating between each animation state much faster
than before and ultimately, look smoother! Alright! Our character will now play a jump
animation every time that we jump and transition back to the proper movement state when it lands!
Take a look at the comparison! Looks great! But now let’s take this to the next level! As mentioned after the analysis of Mario’s jump, we may want to create different trajectories
on consecutive jumps! To do so, we’ll need to calculate three different gravity values and
three different initialJumpVelocity values. To implement each jump we are actually going
to refactor the code from earlier. What we’ll do is create a new integer variable titled
“jumpCount” and we’ll set it to a value of 0. Next, we’ll create two Dictionaries that we’ll
use to store the three initial jump velocities and the three jump gravities. A dictionary is a
way to store specific values behind assigned keys. You need the proper key to see what’s on the
other side. In our case, our keys will be the integers 1, 2 or 3 which represents which jump
the player is on. So in our initialJumpVelocities dictionaries, we’ll be able to access the
first jump’s velocity by using 1 as the key. In C# we need to declare the type of key
that we’ll be using, and the type of value it will store. So we’ll use int and float
and then declare the variable names for each. Then we’ll assign these to two instances
of the exact same type of Dictionaries. To add the keys and values, Dictionaries
have an Add function. We’ll calculate the new trajectories and add them to the dictionaries
inside of our “setupJumpVariables” function. That way these calculations occur
during the awake lifecycle phase. We know the formulas! We need to know how
long we want each jump to take, and the max height that we want each to reach. Let’s say
our first jump remains the same, our second jump adds a value of 2 to the original height and
is 25% longer in time. We’ll plug these into our equations and assign them to new variables called
secondJumpGravity and secondJumpInitialVelocity. Then we’ll create a third jump that adds a value
of 4 to the original height and is 50% longer! We’ll assign these results to variables titled
thirdJumpGravity and thirdJumpInitialVelocity. Next, we’ll add 7 lines of code.
The first three assign the first, second and third initialJumpVelocity values
to the initialJumpVelocities dictionary. And the last four will assign the
jumpGravities with the proper 1 2 or 3 keys and an additional key
of 0 for when JumpCount resets. So what does this do for us? Well, now all we
need to do to change the trajectory of the jump is replace our initialJumpVelocity with the
initialJumpVelocities dictionary and key in with the currentJumpValue. This is done by placing
that variable in brackets next to the Dictionary. And we’ll do the same in handleGravity where
we are currently using the gravity variable. One thing to note is that we
need to increment the jump. We would do so in the same handleJump
function if the conditions to jump are met. So we’ll add jumpCount += 1. But to reset
the value is going to be a little different. Mario appears to have some form of timer
that counts down the moment that he lands each jump. If we wait too long, he
just does his normal jump again. For this, we’re going to try to
use what’s known as a coroutine. A coroutine is a function that, put simply,
is able to pause its execution for a certain amount of time. Let’s write this coroutine
function: typing “IEnumerator jumpResetRoutine”. IEnumerator being the return value for coroutines,
we’ll type in “yield return new WaitForSeconds” and pass in a value of .5. What this means is that
when the coroutine is called, the first thing that will happen is that the function will pause for
half a second before continuing to execute the rest of the code! So after that half a second,
we can reset the jumpCount to a value of 0! And we’ll want to use this coroutine when we land. So in the isJumpAnimating conditional of the
handleGravity function, we’ll write StartCoroutine passing in the invoked jumpResetRoutine
function! This is proper syntax for coroutines. However, what if we don’t want to reset the
jumpCount value because the jump button was pressed before that half a second countdown?
We can actually cancel the coroutine! We’ll need to refer to this coroutine
specifically, so we can declare a new Coroutine variable in the scope of the
class titled “currentJumpResetRoutine” and we will set this equal to our
StartCoroutine code in handleGravity. So at the start of our jumpConditional,
we’ll say if the jumpCount is less than 3 and currentJumpResetRoutine is not equal to
null, then we’ll StopCoroutine and pass in currentJumpResetRoutine! We only do
this if the jumpCount is less than 3 because we would otherwise want to reset the
value restarting the jump sequence anyway! Testing in play mode and Jammo is now
jumping at 3 different heights as long as the jump is pressed within the correct timing!
Otherwise we’ll see that it resets! Perfect! Last but not least, we are going
to need to modify our animator. We’ll go ahead and delete the jump animation
state, right click and select “create sub state machine”. What this does is allow us to more
cleanly transition between our jump animations and the three movement animations. We’ll
go ahead and create the same transitions to the jump state machine that are dependent
on the “isJumping” condition being true, disable hasExitTime, and this time, we’ll set
the transition duration to 0 for a snappier jump! DoubleClicking on the JumpState state machine node
will navigate us into that state. Here, the big difference is that we can lead back to the parent
state which has our three movement animations. Let’s create 4 empty animation states.
The first is going to be the router and it won’t even have an animation. This is our
default state when entering the jump sub state. We’ll then set each jump animation to
the three other empty animation states. Setting up transitions from the router to each
jump state, we’ll disable has exit time and set the transitionDuration again to 0. And now
we’ll need a new parameter that will determine which jump animation to play. We’ll create a new
integer parameter titled “jumpCount”. For the second and third jump animations, jumpCount
needs to be equal to 2 and 3 accordingly. For the first, we’ll want it to be
not equal to 2 and not equal to 3. This serves as a default animation in
case our jumpCount ever surpasses 3. Selecting that third animation where the character
flips, we’re going to want to modify the playback speed so that the animation can be complete
even if the player lets go of the jump button and shortens the jump. Let’s try setting it
to a value of 1.5 and see how that turns out. Lastly, we’ll want to create transitions
from each of these three jump animations and connect them back to the Base Layer node.
But we’ll be given the option to select which movement animation state from the base layer
we want to transition to. As it turns out, we’ll want each of these three jump animations
to potentially transition to all three of the movement states depending on the conditions. So
we’ll create 9 transitions, 3 per jump state, disable has exit time, modify the settings and
assign each with its own set of conditions for the transition to take place. We’ll use .1 again
just like before for the transition duration. isRunning and isWalking will need to be
true and isJumping will need to be false for the transitions from all three
of the jump states to the run state in the base layer. isWalking will need
to be true and isRunning and isJumping false to transition from all three
jumping states to the walking state. And all three will need to be false
to transition back to the idle state. This offers the cleanest transition directly
back to the proper state rather than just starting at idle and working back to the
running state if the character is running. And the very last thing we’ll need to do is
set this jumpCount value in our code! We can do so by creating a new hash just like before:
“JumpCountHash” and set it inside of the Awake function using the StringToHash function. In our
handle gravity function, we’ll check to see if the jumpCount is equal to 3 when we are grounded, if
so we’ll reset to 0 and set that in the animator. And in handle jump, we’ll set the animator
immediately after incrementing the jump count! After all of that work! Here is
our final result for this project! Awesome job! There’s definitely smaller changes
that could make this better, but this is such a solid starting point for a jump mechanic. Of
course, you can modify this and refactor however you see fit! But I hope this has been a helpful
walkthrough of how to make an awesome jump! If you want to help vote on what we’ll cover
next, dashing or strafing animations, be sure to check out the Ultra Supporter and Believer tier
on Patreon! We’d also love to have you as a part of this awesome growing community, we’d love to
have you in the iHeartGameDev channel discord. For updates on the next video, be sure to follow
me on twitter. Links are in the description. But that’s all for today, thank you so much for
watching and I’ll see you in the next video!