How to Prevent Player from Jumping Indefinitely While Holding Spacebar (Bunnyhopping)

This should really be a simple solution, and yet I’ve been struggling on why my jump button isn’t working as intended. The goal is for the player to press the jump button (spacebar) and even while holding it, the player character should still only jump ONCE. Then, the player should be able to jump again once they release and press the jump button again. However, instead the player character is able to jump up and down, over and over again while holding the jump button. It’s not flying or floating in the air. Just jumping up and down to the ground again. Forever.

An easy solution to this would be to just move this to Update() and use Input.GetKeyDown(“space”), but I want to use FixedUpdate() for physics-related things and I can’t use the GetKeyDown for FixedUpdate() else it doesn’t work as intended. I’ve looked through several past Unity Discussion posts along with multiple videos online to learn how they implemented their jump movement and where I went wrong, but most of them cover either the flying problem I mentioned before or that the player character can only jump once and never again (which I’ve gotten that same problem too when I tried figuring out what was wrong). I’ve introduced multiple boolean variables and integer variables to check how many times the player has jumped or if the player has jumped while holding the spacebar, but I must not be doing something right because those options haven’t worked for me. I think I just need a fresh pair of eyes to tell me what I’m doing wrong.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D body;

    [SerializeField] float moveSpeed = 10;

    private float jumpHeight = 5;
    [SerializeField] float gravityScale = 15;
    [SerializeField] float fallGravityScale = 20;

    bool isGrounded;

    // Start is called before the first frame update
    void Start()
    {
        body = GetComponent<Rigidbody2D>();

        isGrounded = false;
    }

    //For physics, we want to use FixedUpdate() instead of Update().
    void FixedUpdate()
    {
        //By using GetAxisRaw, the player will hard stop when they release the movement keys, providing them more control
        float horizontalInput = Input.GetAxisRaw("Horizontal");
        body.velocity = new Vector2(horizontalInput * moveSpeed, body.velocity.y);

        if (Input.GetKey("space") && isGrounded)
        {
            PlayerJump();
        }

        if (body.velocity.y > 0)
        {
            body.gravityScale = gravityScale;
        }
        else
        {
            body.gravityScale = fallGravityScale;
        }
    }

    void PlayerJump()
    {
        body.gravityScale = gravityScale;
        float jumpForce = Mathf.Sqrt(jumpHeight * (Physics2D.gravity.y * body.gravityScale) * -2) * body.mass;
        body.velocity = new Vector2(body.velocity.x, jumpForce);

        isGrounded = false;
    }

    
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ground")
        {
            isGrounded = true;
        }
        
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        isGrounded = false;
    }
}

Use Input.GetKeyDown rather than GetKey.

1 Like

As spiney199 mentioned you should use the Input.GetKeyDown, what do you mean it doesn’t work as intended? In any case here are two things you can try:

  1. Use the Input.GetKeyDown in the Update and have it change a boolean ex. shouldJump then check that boolean in the fixedUpdate and if true call the PlayerJump method and inside it reset the boolean to false.
  2. If you insist that you don’t want to use the Input.GetKeyDown then have a check for Input.GetKeyUp that sets a boolean ex. keyHasBeenReleased, then check in the PlayerJump method also if that boolean is true to be able to perform a jump and set it to false whenever the jump is happening.

Finally, you have a bug in your code, when you call the PlayerJump method you make the isGrounded false and you reset it to true when the gameObject enters a collider with the tag Ground, but there can be situations that the player won’t jump, for example if there is an obstacle above him, so he will never leave the collider. In those situations the isGrounded will become false but the player will never get inside the OnCollisionEnter because he never left the ground collider, this will result in a situation where the player won’t be able to jump anymore.

I guess the actual issue here is polling input in FixedUpdate. You should only ever poll input in Update.

Poll the input in Update, apply the inputs in FixedUpdate.

1 Like

Thank you so much you two! The problem is now solved. I was getting the issue with Input.GetKeyDown before because I was putting it in FixedUpdate(). I changed my code based on your guys’ feedback and it works now! I’ll remember to poll my inputs in Update() from now on.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D body;

    [SerializeField] float moveSpeed = 10;

    private float jumpHeight = 5;
    [SerializeField] float gravityScale = 15;
    [SerializeField] float fallGravityScale = 20;

    bool isGrounded;
    bool isJumping;

    void Start()
    {
        body = GetComponent<Rigidbody2D>();

        isGrounded = false;
        isJumping = false;
    }

    //Player inputs go here
    private void Update()
    {
        if (Input.GetKeyDown("space"))
        {
            isJumping = true;
        }
    }

    //For physics, we want to use FixedUpdate() instead of Update().
    void FixedUpdate()
    {
        //By using GetAxisRaw, the player will hard stop when they release the movement keys, providing them more control
        float horizontalInput = Input.GetAxisRaw("Horizontal");
        body.velocity = new Vector2(horizontalInput * moveSpeed, body.velocity.y);

        if (isJumping && isGrounded)
        {
            PlayerJump();
        }

        if (body.velocity.y > 0)
        {
            body.gravityScale = gravityScale;
        }
        else
        {
            body.gravityScale = fallGravityScale;
        }
    }

    void PlayerJump()
    {
        body.gravityScale = gravityScale;
        float jumpForce = Mathf.Sqrt(jumpHeight * (Physics2D.gravity.y * body.gravityScale) * -2) * body.mass;
        body.velocity = new Vector2(body.velocity.x, jumpForce);

        //Commenting isGrounded out because this can cause a problem
        //if the player were to jump and an obstacle were above them

        //isGrounded = false;
        isJumping = false;
    }

    
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ground")
        {
            isGrounded = true;
        }
        
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        isGrounded = false;
    }
}

1 Like