Character Controller Jump is a teleport instead of "smooth" jump

Heii there :slight_smile:

I just started learning C# and general game development and at the moment i try to make a movement script for the player. I could use Rigidbody but i would like to handle some of the stuff my way.

Everything works fine… except… the jump. I always try to find and fix errors by myself but sadly i can’t find the bug here. The jump is like a teleport instead of a smooth jump… but tbh… i am not sure if this version of the script works. I tried so many ideas… and i also looked at so many examples i don’t get it and the frustration is kicking in very hard right now. I would love to get a little hint from someone. A sweet little hint that pushes me towards the right direction. :slight_smile:

Thanks in advance <3

public class CharacterMovement : MonoBehaviour
    {
        private CharacterController characterController;
        private bool isSprinting = false;
        private bool isGoingBackwars = false;
        private bool isGrounded = false;
 
        private float currentGravity = 0.0f;
        private float jumpVelocity = 0.0f;
 
        [Range(0.1f, 100.0f)]
        public float moveSpeed = 1.0f;
 
        [Range(0, 500)]
        public int sprintBoostPercentage = 75;
 
        [Range(0, 100)]
        public int runningBackwardsPaneltyPercentage = 50;
 
        [Range(0.1f, 500.0f)]
        public float jumpStrength = 2.0f;
 
        [Range(0, 100)]
        public int inAirPaneltyPercentage = 90;
 
        [Range(0.1f, 100.0f)]
        public float gravity = 2.0f;
 
        [Range(0.1f, 500.0f)]
        public float maxGravity = 10.0f;
 
        private void Awake()
        {
            characterController = GetComponent<CharacterController>();
        }
 
        private void Update()
        {
            isGoingBackwars = CheckPlayerGoingBackwards();
            isSprinting = CheckPlayerSprinting();
            isGrounded = characterController.isGrounded;
            MovePlayer();
        }
 
        private void MovePlayer()
        {
            float inputHorizontal = Input.GetAxis("Horizontal");
            float inputVertical = Input.GetAxis("Vertical");
 
            Vector3 move = (transform.forward * inputVertical + transform.right * inputHorizontal) * (moveSpeed / 8.0f);
 
            if (!isGrounded)
                move *= (1.0f - inAirPaneltyPercentage / 100.0f);
            else if (isGoingBackwars)
                move *= (1.0f - runningBackwardsPaneltyPercentage / 100.0f);
            else if (isSprinting)
                move *= (1.0f + sprintBoostPercentage / 100.0f);
 
            move = ApplyGravity(move);
            characterController.Move(move * Time.deltaTime);
            move = Jump(move);
        }
 
        private Vector3 ApplyGravity(Vector3 move)
        {
            if (!isGrounded)
            {
                currentGravity += Mathf.Sqrt((gravity * Time.fixedDeltaTime));
                currentGravity = Mathf.Clamp(currentGravity, 0.0f, maxGravity);
            }
            else
            {
                currentGravity = 0.0f;
            }
 
            move.y -= currentGravity;
            return move;
        }
 
        private Vector3 Jump(Vector3 move)
        {
            if (Input.GetButton("Jump") && isGrounded)
            {
                move.y += jumpStrength;
            }
 
            return move;
        }
 
        private bool CheckPlayerSprinting()
        {
            return Input.GetButton("Sprint");
        }
 
        private bool CheckPlayerGoingBackwards()
        {
            return Input.GetAxis("Vertical") <= -1.0f;
        }
    }

Your problem is primarily from line 50 where you create a fresh move variable each frame.

This wipes out any vertical velocity you might have had going from a jump.

One way to refactor might be to make move be a class-level variable, then ONLY set the X and Z components of it and leave the Y alone so that over time it can send you up and then gravity brings you back down.

Don’t worry though because even the CharacterController example that Unity provides in their example code is now unfortunately broken, but I was able to fix it all here, and you’re welcome to take a peek or else just use it yourself:

CharacterController CharMover broken:

I wrote about this before: the Unity example code in the API no longer jumps reliably. I have reported it. Here is a work-around:

2 Likes

One easy way to refactor this, after you have made the move variable be a class-level variable, is to do this:

// this is basically your code above assigned to a different variable
Vector3 lateralMove = (transform.forward * inputVertical + transform.right * inputHorizontal) * (moveSpeed / 8.0f);

// now copy only the X and Z parts to the move variable
move.x = lateralMove.x;
move.z = lateralMove.z;

That’s a cheap and cheerful way to leave the Y unmolested by your frame-to-frame control inputs, allowing JUMP to send it upwards, and allowing gravity to pull it down.

1 Like

Thank you soooooooo much. It hurts that this was super obvious… :frowning:

Should have seen that.

Was able to fix everything now and also looked into your thread. :slight_smile:

public class CharacterMovement : MonoBehaviour
{
    [Range(0.1f, 100.0f)]
    public float moveSpeed = 1.0f;

    [Range(0, 500)]
    public int sprintBoostPercentage = 75;

    [Range(0, 100)]
    public int runningBackwardsPaneltyPercentage = 50;

    [Range(1.0f, 250.0f)]
    public float jumpStrength = 10.0f;

    [Range(0, 100)]
    public int inAirPaneltyPercentage = 90;

    [Range(0.1f, 10.0f)]
    public float gravity = 2.0f;

    [Range(0.1f, 50.0f)]
    public float maxGravity = 10.0f;

    private CharacterController characterController;
    private bool isSprinting = false;
    private bool isGoingBackwars = false;
    private bool isGrounded = true;

    private float verticalVelocity = 1.0f;
    private Vector3 move = Vector3.zero;

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

    private void Update()
    {
        CheckPlayerGoingBackwards();
        CheckPlayerSprinting();
        isGrounded = characterController.isGrounded;
        MovePlayer();
    }

    private void MovePlayer()
    {
        float inputHorizontal = Input.GetAxis("Horizontal");
        float inputVertical = Input.GetAxis("Vertical");

        Vector3 lateralMove = (transform.forward * inputVertical + transform.right * inputHorizontal) * (moveSpeed / 8.0f);

        if (!isGrounded)
            lateralMove *= (1.0f - inAirPaneltyPercentage / 100.0f);
        else if (isGoingBackwars)
            lateralMove *= (1.0f - runningBackwardsPaneltyPercentage / 100.0f);

        if (isSprinting)
            lateralMove *= (1.0f + sprintBoostPercentage / 100.0f);

        move.x = lateralMove.x;
        move.z = lateralMove.z;

        ApplyGravity();
        ApplyJump();
        characterController.Move(move * Time.deltaTime);
    }

    private void ApplyGravity()
    {
        if (isGrounded)
        {
            verticalVelocity = 0.0f;
        }

        verticalVelocity -= Mathf.Sqrt(gravity * Time.fixedDeltaTime);
        verticalVelocity = Mathf.Clamp(verticalVelocity, -maxGravity, float.MaxValue);

        move.y = verticalVelocity;
    }

    private void ApplyJump()
    {
        if (Input.GetButton("Jump") && isGrounded)
        {
            verticalVelocity += Mathf.Sqrt(jumpStrength * 2.0f * gravity);
            move.y = verticalVelocity;
        }
    }

    private void CheckPlayerSprinting()
    {
        if (Input.GetButtonDown("Sprint") && isGrounded)
            isSprinting = true;
        else if (Input.GetButtonUp("Sprint"))
            isSprinting = false;
    }

    private void CheckPlayerGoingBackwards()
    {
        isGoingBackwars = Input.GetAxis("Vertical") <= -1.0f;
    }
}
2 Likes

Awesome! And Merry Christmas. Also, I recommend you put open/close braces into the if/else statements starting from line 52 to line 58… while it is technically legal to have single-line actions resulting from if/else constructs, it’s safer to put curly braces around the actions to remind you precisely what code the if is controlling.

1 Like

Thanks and merry christmas :slight_smile:

I actually do it in java… all the time… i mean the brackets. I only don’t use the brackets if i am 100% certain that i cant mess that part up :smile: Thanks for the advice tho.

1 Like