My Player character jumps very very high sometimes

Hello,

I wrote some code to move player around with arrow keys and using physics in 3d environment. Now I added code for jumping and i have a problem that i can’t solve alone. Note: I’m not a pro programmer, I’m more or less a newbie.

Scenarios:

  1. Player is stationary, Jump key is pressed once - player jumps once. OK
  2. Player is stationary, spamming jump key - huge jump !!! Not OK
  3. Moving player around with arrow keys and jumping (spamming or just normally pressing jump) - consistent jumps. OK

When player jumps, I cast a ray downwards and check if ray hits the ground layer. This way multiple jumps in the air are disabled. I do that in function AreWeBackOnGround().

I hope I provided enough information for you. If not, feel free to ask me questions. Cheers!

Here is the code:

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
using static Unity.VisualScripting.Member;



public class Mover : MonoBehaviour
{
    // Serialized Fields
    [SerializeField] float walkSpeed = 100f;
    [SerializeField] float runSpeed = 170f;
    [SerializeField] float rotationSpeed = 5f;
    [SerializeField] float acceleration = 3f;
    [SerializeField] float drag = 5f;
    [SerializeField] float jumpForce = 5000f;
    [SerializeField] float JumpFallMultiplier = 2.5f;
    [SerializeField] float groundCheckRadius = 0.2f;
    [SerializeField] LayerMask groundLayer;

    // Private Variables
    private float chosenSpeed;
    private float AnimatorSpeed; // Speed that is sent to Animator
    private float xInput;
    private float zInput;
    private float idleTime;
    private Quaternion previousRotation;
    private Quaternion desiredRotation = Quaternion.identity;
    private Rigidbody PlayerRigidBody;

    // Constants
    private Quaternion UpRotation = Quaternion.Euler(0f, 0f, 0f);
    private Quaternion DownRotation = Quaternion.Euler(0f, 180f, 0f);
    private Quaternion LeftRotation = Quaternion.Euler(0f, 270f, 0f);
    private Quaternion RightRotation = Quaternion.Euler(0f, 90f, 0f);

    // State Variables
    private int isJumping = 0;
    private bool IsPlayerMovingHorizontally = false;

    // References
    public Animator animator;

    bool jumpKeyIsPressed = false;
    void Start()
    {
        CheckIfThereIsRigidBody();
    }

    void Update()
    {
        GetInputKeys();
        ToggleBetweenRunAndWalkSpeed();

        animator.SetFloat("Speed", AnimatorSpeed);
        animator.SetFloat("idleTime", idleTime);
    }

    // All Physics stuff happens in fixed update
    void FixedUpdate()
    {
        MovePlayer();
        PlayerJump();
        RotatePlayer(); // Rotate according to user input
    }

    void GetInputKeys()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            jumpKeyIsPressed = true;
        }

        // Get horizontal/vertical input
        xInput = Input.GetAxis("Horizontal");
        zInput = Input.GetAxis("Vertical");

        DetermineDesiredRotationFromArrowKeys();

    }

    void ToggleBetweenRunAndWalkSpeed()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            chosenSpeed = walkSpeed;
            AnimatorSpeed = chosenSpeed;
        } else if (xInput == 0f && zInput == 0f) //if we are not moving
        {
             AnimatorSpeed = 0;

        } else
        {
            chosenSpeed = runSpeed;
            AnimatorSpeed = chosenSpeed;
        }       
    }
    void MovePlayer()
    {
        //Check if any movement keys are pressed
        if (xInput != 0f || zInput != 0f)
        {
            ApplyHorizontalMovementForces();

            // Reset idleTime timer
            idleTime = 0;

            IsPlayerMovingHorizontally = true;
        }
        else //idle
        {
            IsPlayerMovingHorizontally = false;
            idleTime += Time.deltaTime;
        }
    }
    void PlayerJump()
    {
        if (isJumping == 1)
        {
            Debug.Log("We are still jumping");

            if (AreWeBackOnGround() && PlayerRigidBody.velocity.y < 0.1f)
            {
                //We are not jumping anymore
                isJumping = 0;
                Debug.Log("End of jump");
                animator.SetInteger("isJumping", isJumping);

                jumpKeyIsPressed = false; //reset jump key indicator

            } else
            {
                Debug.Log("End of jump? No, apply gravity.");

                // Apply constant downward force for gravity
                PlayerRigidBody.AddForce(Vector3.down * -Physics.gravity.y * JumpFallMultiplier, ForceMode.Acceleration);

                if (IsPlayerMovingHorizontally)
                {
                    Debug.Log("End of jump? No, apply gravity. moving on x or z axis? - apply additional gravity");

                    //Apply additional downward force
                    // Calculate current horizontal velocity magnitude
                    float horizontalVelocityMagnitude = PlayerRigidBody.velocity.magnitude;

                    // Adjust JumpFallMultiplier based on velocity (modify as needed)
                    float adjustedJumpFallMultiplier = JumpFallMultiplier + horizontalVelocityMagnitude * 0.6f;

                    // Apply gravity with adjusted multiplier
                    PlayerRigidBody.AddForce(Vector3.down * -Physics.gravity.y * adjustedJumpFallMultiplier, ForceMode.Acceleration);
                }
            }
        }
        else if (jumpKeyIsPressed)
        {
            if (IsPlayerMovingHorizontally)
            {
                // Aply much bigger force, if player is already moving in any direction (except y)
                PlayerRigidBody.AddForce((jumpForce / 0.9f) * Vector3.up, ForceMode.Impulse);
                Debug.Log("Pressed jump. Bigger force");

            }
            else
            {
                PlayerRigidBody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
                Debug.Log("Pressed jump. Normal force");
            }
           
            isJumping = 1;
            animator.SetInteger("isJumping", isJumping);
            idleTime = 0;           
        }
    }
       
    void ApplyHorizontalMovementForces()
    {
        // Calculate desired velocity based on input and move speed
        Vector3 desiredVelocity = new Vector3(xInput, 0, zInput).normalized * chosenSpeed;

        // Apply force towards desired velocity, taking into account acceleration and drag
        Vector3 force = (desiredVelocity - PlayerRigidBody.velocity) * acceleration - PlayerRigidBody.velocity * drag;
        PlayerRigidBody.AddForce(force, ForceMode.Acceleration);

        Debug.Log("Here we are at end of ApplyHorizontalMovementForces()");

    }

    bool AreWeBackOnGround()
    {
        // Cast a ray downwards from the player's center
        Vector3 rayOrigin = transform.position + Vector3.up * 0.1f; // Offset slightly above
        Ray ray = new Ray(rayOrigin, Vector3.down);

        Debug.Log("Here we are at AreWeBackOnGround");


        // Check if the ray hits the ground layer within the specified radius
        return Physics.Raycast(ray, groundCheckRadius, groundLayer);
    }

    void RotatePlayer()
    {
        // Store previous rotation for next frame
        previousRotation = transform.rotation;

        // Smoothly rotate towards the desired rotation over time
        transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, Time.deltaTime * rotationSpeed);
    }

    void DetermineDesiredRotationFromArrowKeys()
    {
        // Check for single key presses
        if (Input.GetKey(KeyCode.RightArrow) && !Input.GetKey(KeyCode.UpArrow) && !Input.GetKey(KeyCode.DownArrow))
        {
            desiredRotation = RightRotation;
        }
        else if (Input.GetKey(KeyCode.LeftArrow) && !Input.GetKey(KeyCode.UpArrow) && !Input.GetKey(KeyCode.DownArrow))
        {
            desiredRotation = LeftRotation;
        }
        else if (Input.GetKey(KeyCode.UpArrow) && !Input.GetKey(KeyCode.RightArrow) && !Input.GetKey(KeyCode.LeftArrow))
        {
            desiredRotation = UpRotation;
        }
        else if (Input.GetKey(KeyCode.DownArrow) && !Input.GetKey(KeyCode.RightArrow) && !Input.GetKey(KeyCode.LeftArrow))
        {
            desiredRotation = DownRotation;
        }

        // Check for diagonal combinations
        else if (Input.GetKey(KeyCode.RightArrow) && Input.GetKey(KeyCode.UpArrow))
        {
            desiredRotation = Quaternion.Slerp(UpRotation, RightRotation, 0.5f);
        }
        else if (Input.GetKey(KeyCode.RightArrow) && Input.GetKey(KeyCode.DownArrow))
        {
            desiredRotation = Quaternion.Slerp(RightRotation, DownRotation, 0.5f);
        }
        else if (Input.GetKey(KeyCode.LeftArrow) && Input.GetKey(KeyCode.UpArrow))
        {
            desiredRotation = Quaternion.Slerp(UpRotation, LeftRotation, 0.5f);
        }
        else if (Input.GetKey(KeyCode.LeftArrow) && Input.GetKey(KeyCode.DownArrow))
        {
            desiredRotation = Quaternion.Slerp(LeftRotation, DownRotation, 0.5f);
        }
        else
        {
            desiredRotation = previousRotation;
        }
    }
    void CheckIfThereIsRigidBody()
    {
        PlayerRigidBody = GetComponent<Rigidbody>();
        if (PlayerRigidBody == null)
        {
            Debug.LogError("Mover script requires a Rigidbody component attached to the game object.");
            return;
        }
    }
}

This is the video of the problem:

You’re checking for input in FixedUpdate(). This is a big no-no, since FixedUpdate may be called zero, one, or multiple times per frame - as needed for it to be called at a fixed frequency. If you check for input there it means some frames you might miss input altogether, and some frames you might process input multiple times, leading to inconsistent control.

As stated in the manual, always check for input in Update (there are exceptions to this rule, but your case is not one of them):

Then, react to that input in FixedUpdate() if you need to.

That’s a great advice, thanks for pointing it out :slight_smile: I edited my code and updated it in my first post. Now I check for input keys in Update(). I created new bool variable. Now I set it to true in Update() (if jump key is pressed). Then I set it to False at the end of the jump routine in fixedUpdate().

Sadly, this doesn’t resolve my problem with high jump

The culprit is not obvious to spot in your code, so my advice would be to debug step by step: place a breakpoint in PlayerJump() and see what happens when jumpKeyIsPressed every frame, that will give you a clearer picture of the problem.

My guess would be that on the frame immediately after jumping, the rigidbody’s vertical velocity is still < 0.1 and it’s still on the ground, so it is allowed to jump again.

I tried changing values of constant in (vertical velocity is still < 0.1) from 0.001 to 1 and it didn’t make a difference (for the better).

So now after some debugging I decided that code should be working and something else is the problem, so I attached a copy of this script to a 3d Cube and it worked as intended. The only difference between original script and of a copy is that I deleted all Animator stuff in a copy, since Cube doesn’t need it. Everything else is the same. So that is out of my scope of knowledge right now, I just learned-ish how to use animations with the code (for transitions between different states) (and downloaded free 3d skeleton with premade animations to run, jump etc…). I use three parameters (speed, idleTime and isJumping) to transition between states. I disabled only “Has exit time” in all transitions between states.

Back from cube to original model: I just set Controller in my Animator to None, script works as intended with tweaking of all paramaters of move script. Arrghhhh. So somehow these animations “fight” with my code. I don’t have much understanding of how animator works and how it can affect physics, so yeah… It would be great if anyone knew an easy fix :smile:
It’s also weird that when my player is in idle state (animation), pressing jump key once triggers that huge jump every time. So my guess is that Animator have some control of physics on my skeleton Player… And occurrence of huge jump is actually normal jump with that force applied. That would also explain this: If i disable in inspector → Animator ->ApplyRootMotion, jump in (NoRootMotion state) is always that high.

root motion is used to move objects with the animation’s movements :slight_smile: think of it as an alternative to moving things with c# or physics

Yeah, thanks. That was my main problem (lack of this knowledge). I learned much more about animator and the concept of animation driven movement these days. Now I feel pretty silly that I spent two whole days trying to fix the"problem", when in reality it was as simple as disabling root motion and reconfiguring transition conditions in animator and movement parameters in my script heh :roll_eyes: