Making a Character Jump When a Button is Pressed - Unity Game Development Tutorial

In this Unity game development tutorial we're going to look at how we can make a character jump when a button is pressed.

You can either watch the video version below or continue reading for written instructions.

OK, we'll start with this scene that has a character and some obstacles. 

A Character Moving Around the Scene

The character has a Character Controller component attached, that is used to move the character around and handle collisions with the obstacles. We've also set up a Cinemachine camera to follow our character around.

We created this scene in our 'Follow Camera' tutorial, so take a look if you want to know how it was done.

Let's double click on the PlayerMovement script and have a look at the current state of our script.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed;
    public float rotationSpeed;

    private CharacterController characterController;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
    }

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        characterController.SimpleMove(movementDirection * magnitude);

        if (movementDirection != Vector3.zero)
        {
            Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);

            transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
        }
    }
}

Now we need to add in some additional logic for jumping. We want to be able to control the amount that our character jumps, so we'll add a public field for the jump speed.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed;
    public float rotationSpeed;
    public float jumpSpeed;

    ...
    

We also want our character to come back down again after jumping, so we need to apply gravity to our character each frame. We'll add a private field to keep track of the speed in the Y direction. 

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed;
    public float rotationSpeed;
    public float jumpSpeed;

    private CharacterController characterController;
    private float ySpeed;
    
    ...

We'll increase this value when the character jumps and decrease it over time according to gravity.

Let's go to our Update method to add the jump logic. First of all, we'll adjust for gravity.

    ...

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        ySpeed += Physics.gravity.y * Time.deltaTime;

        ...

We're getting the Physics gravity value and adding this amount to our Y speed every second. Remember, Time.deltaTime is the amount of seconds that have passed since the last frame. By default, the physics gravity value is set to minus 9.81 in the Y direction, so this line will decrease our Y speed by 9.81 every second.

Next, we'll check if the Jump button has been pressed. If it has, we'll set the Y speed to our jump speed.

    ...

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        ySpeed += Physics.gravity.y * Time.deltaTime;

        if (Input.GetButtonDown("Jump"))
        {
            ySpeed = jumpSpeed;
        }
        
        ...

Now we need to use our Y speed when moving the character. To do this, we need to extract the calculation that we're passing into our SimpleMove method and assign it to a variable. 

    ...

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        ySpeed += Physics.gravity.y * Time.deltaTime;

        if (Input.GetButtonDown("Jump"))
        {
            ySpeed = jumpSpeed;
        }
        
        Vector3 velocity = movementDirection * magnitude;
        
        characterController.SimpleMove(velocity);

        ...

We can then set the Y value of this velocity variable to our Y speed.

        ...

        if (Input.GetButtonDown("Jump"))
        {
            ySpeed = jumpSpeed;
        }
        
        Vector3 velocity = movementDirection * magnitude;
        velocity.y = ySpeed;

        characterController.SimpleMove(velocity * Time.deltaTime);

        ...

SimpleMove is only for simple movement and ignores any movement in the Y direction, so we need to switch to using the Move method. When using the Move method, we also have to multiply by Time.deltaTime to ensure the character moves at the same speed regardless of the frame rate. 

        ...

        Vector3 velocity = movementDirection * magnitude;
        velocity.y = ySpeed;

        characterController.Move(velocity * Time.deltaTime);

        ...

Let's save the script and switch back to Unity.

We'll select the character in the hierarchy and set the jump speed to 5.

Setting the jump speed to 5

Let's press play to try it out. By default, jump is mapped to the spacebar, and if we press it now, our character will jump.

Pressing the spacebar to make the character jump

This is a good start but there are a couple of issues with our script at the moment. The first is that we can keep pressing the spacebar and our character will keep rising higher and higher.

Charater rising and rising

The other is, if we go up the slope and then fall off the other side, the character instantly snaps to the floor.

The character snapping to the floor

Let's switch to the Debug view of the Inspector to see what's going on.

Switching to the debug inspector

In here, we can see the value of our private fields, and we can see that the value of our Y speed keeps decreasing, which is why our character falls so fast.

The Y speed decreasing

We can fix both of these issues by checking if our character is on the ground. We only want the character to jump if it's on the ground, and we only want it to accelerate under gravity if it's not on the ground.

Let's stop the game and switch back to the script to fix this. 

The character controller has a flag that indicates if it is on the ground or not. We'll wrap our jump logic with a check on this flag.

    ...

    Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        ySpeed += Physics.gravity.y * Time.deltaTime;

        if (characterController.isGrounded)
        {
            if (Input.GetButtonDown("Jump"))
            {
                ySpeed = jumpSpeed;
            }
        }
    
        Vector3 velocity = movementDirection * magnitude;
        velocity.y = ySpeed;

        characterController.Move(velocity * Time.deltaTime);

        ...

Now the character will only jump if it's on the ground.

We also want to stop the Y speed increasing indefinitely, so when our character is on the ground, we'll reset the Y speed back to zero.

        ...

        if (characterController.isGrounded)
        {
            ySpeed = 0;

            if (Input.GetButtonDown("Jump"))
            {
                ySpeed = jumpSpeed;
            }
        }
        
        ...

Let's save this and switch back to Unity to try it out.

Now if we move up the slope and fall off, the character falls properly.

The character falling off the ledge correctly

However, we've now got another issue. If we keep pressing the spacebar it's hit and miss whether the character will jump or not.

If we look at the debug inspector, we can get a clue as to why. The Y speed keeps fluctuating even though the character is on the ground. 

The Y speed fluctuating

This is because the IsGrounded flag is a little bit temperamental.

We can easily fix this though. Let's stop the game and switch back to the script.

Instead of resetting the Y speed to zero when it's on the ground, we'll set it to minus 0.5. 

        ...

        if (characterController.isGrounded)
        {
            characterController.stepOffset = originalStepOffset;
            ySpeed = -0.5f;

            if (Input.GetButtonDown("Jump"))
            {
                ySpeed = jumpSpeed;
            }
        }
        
        ...

This small negative amount seems to be enough to keep the character on the ground.

Let's save the script and switch back to Unity to try this out.

We can see in the debug view that the Y speed is now stable.

The Y Speed now stable

And if we keep pressing the spacebar the character jumps consistently.

The character jumping consistently

There's one final quirk that we need to resolve. If we move the character up the steps and jump into the wall, we get a strange jerky movement. 

This seems to be a glitch around the step offset in the character controller. We can fix this by setting the step offset to zero when the character is not on the ground. This is ok as we don't need the character to step up when it is in the air.

Let's stop the game and switch back to the script.

We'll create a private field to hold the original step offset.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed;
    public float rotationSpeed;
    public float jumpSpeed;

    private CharacterController characterController;
    private float ySpeed;
    private float originalStepOffset;

    ...

And we'll set this in the Start method.

    ...

    void Start()
    {
        characterController = GetComponent<CharacterController>();
        originalStepOffset = characterController.stepOffset;
    }

    ...

We'll add an 'else' statement to our grounded check so that we can set the step offset to zero when the character is not on the ground.

        ...

        if (characterController.isGrounded)
        {
            ySpeed = -0.5f;

            if (Input.GetButtonDown("Jump"))
            {
                ySpeed = jumpSpeed;
            }
        }
        else
        {
            characterController.stepOffset = 0;
        }

        ...

Then when the character is on the ground we'll set it back to the original value.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed;
    public float rotationSpeed;
    public float jumpSpeed;

    private CharacterController characterController;
    private float ySpeed;
    private float originalStepOffset;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
        originalStepOffset = characterController.stepOffset;
    }

    void Update()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
        float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
        movementDirection.Normalize();

        ySpeed += Physics.gravity.y * Time.deltaTime;

        if (characterController.isGrounded)
        {
            characterController.stepOffset = originalStepOffset;
            ySpeed = -0.5f;

            if (Input.GetButtonDown("Jump"))
            {
                ySpeed = jumpSpeed;
            }
        }
        else
        {
            characterController.stepOffset = 0;
        }

        Vector3 velocity = movementDirection * magnitude;
        velocity.y = ySpeed;

        characterController.Move(velocity * Time.deltaTime);

        if (movementDirection != Vector3.zero)
        {
            Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);

            transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
        }
    }
}

Let's save the script and switch back to Unity to try it out.

Now when we jump into the wall, the character slides down smoothly.

The character jumping and sliding down the wall

We've now got our character moving and jumping!

That covers everything for this tutorial. We hope that you found it useful. Please leave any questions or feedback in the comments below, and don't forget to subscribe to get notified when we publish our next post.

Thanks.

Comments

Popular posts from this blog

Rotating a Character in the Direction of Movement - Unity Game Development Tutorial

Creating Terrain from Heightmaps - Unity Game Development Tutorial

Simple Movement using Gamepad and Keyboard - Unity Game Development Tutorial