My jump physics are acting unpredictable.

Hello.
I’ve been working on a 3D Infinite Runner for the past week but came to an issue when it came to the physics of my character.

I’m having a few issues relating to the way my Rigidbody-controlled character jumps:

  1. The jumps are unpredictable
    Whenever my character jumps, it either jumps really low, to the height it’s supposed to, above the height it’s supposed to, or waaaaaaay above the height it’s supposed to and flies off the playing field and never comes back down because it is so high.

  2. The physics act slightly different on different devices
    The next issue comes from the jumps acting differently on different devices. A jump height of 100 gets me a way different jump height in the editor than in the Build. I made sure to use Time.deltaTime, which helped a little, but the difference is still there.

Here is my player code if it helps:

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

public class PlayerMove : MonoBehaviour
{
    private PlayerInput inputActions;

    [SerializeField] bool tapped;
   
    [SerializeField] Vector2 touchDelta;

    public float speed;
    public float jumpHight;
    public float downForce;
   
    public Rigidbody rb;
    public bool isGrounded;
    bool jump;


    // Start is called before the first frame update
    void Start()
    {
        inputActions = new PlayerInput();
        inputActions.Enable();

        rb = GetComponent<Rigidbody>();


    }

    // Update is called once per frame
    void Update()
    {
        //determine if we are touching the screen
        if(inputActions.Move.InitialTap.phase == InputActionPhase.Performed)
        {
            tapped = true;
        }
        else
        {
            tapped = false;
        }
  
        if(tapped)
        {
           
            touchDelta = inputActions.Move.TouchDelta.ReadValue<Vector2>();
            float clampedX = Mathf.Clamp(transform.position.x, -9, 9);
            transform.position = new Vector3(clampedX, transform.position.y, transform.position.z);



            //Move our player left or right if the absolute value of touchDelta.x value is greater than the absolute value of touchDelta.y
            if(Mathf.Abs(touchDelta.x) > Mathf.Abs(touchDelta.y))
            {
                rb.AddForce(Vector3.right * touchDelta.x * speed * Time.deltaTime, ForceMode.Impulse);
            }
          
            if (touchDelta.y > touchDelta.x && touchDelta.y > 2.5 && isGrounded)
            {
                print("jump");
                jump = true;
            }
            else
            {
                jump = false;
            }

        }


    }

    void FixedUpdate()
    {
        RaycastHit hit;
        Physics.Raycast(transform.position, Vector3.down, out hit, 0.5f);

        if(hit.collider != null)
        {
            isGrounded = true;
        }
        else
        {
            isGrounded = false;
        }


        rb.AddForce(Vector3.down * downForce * Time.deltaTime, ForceMode.Force);
        if (jump)
        {
            rb.AddForce(Vector3.up * jumpHight * Time.deltaTime, ForceMode.Impulse);
        }

    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Obstacle"))
        {
            GameManager.GameOver();
        }
    }
}

And I’m also attaching a screenshot of the Ridgidbody settings I’m using:

first thing i noticed is you’re using addforce in update, which is a big no no. only adjust rigidbodies in fixedupdate.

you’re also using .deltaTime which is what fixedupdate is made specifically to not have to use.

fixedupdate is a set update rate so you’re changing things that don’t need to be. i don’t remember if it returns the deltatime of update or fixedupdate when in fixedupdate, but either way you don’t need it EVER in fixedupdate.

use deltaTime exclusively in update or coroutines mostly

Ok, I changed every ridgidbody-related thing to fixed updated and removed deltaTime, but it still doesn’t work.
There’s still a really weird height variation thing going on.

Here is the updated code:

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

public class PlayerMove : MonoBehaviour
{
    private PlayerInput inputActions;

    [SerializeField] bool tapped;
   
    [SerializeField] Vector2 touchDelta;

    public float speed;
    public float jumpHight;
    public float downForce;
   
    public Rigidbody rb;
    public bool isGrounded;
    bool jump;


    // Start is called before the first frame update
    void Start()
    {
        inputActions = new PlayerInput();
        inputActions.Enable();

        rb = GetComponent<Rigidbody>();


    }

    // Update is called once per frame
    void Update()
    {
        //determine if we are touching the screen
        if(inputActions.Move.InitialTap.phase == InputActionPhase.Performed)
        {
            tapped = true;
        }
        else
        {
            tapped = false;
        }
  
        if(tapped)
        {
           
            touchDelta = inputActions.Move.TouchDelta.ReadValue<Vector2>();
            float clampedX = Mathf.Clamp(transform.position.x, -9, 9);
            transform.position = new Vector3(clampedX, transform.position.y, transform.position.z);


            if (touchDelta.y > touchDelta.x && touchDelta.y > 2.5 && isGrounded)
            {
                print("jump");
                jump = true;
            }
            else
            {
                jump = false;
            }

        }


    }

    void FixedUpdate()
    {
        RaycastHit hit;
        Physics.Raycast(transform.position, Vector3.down, out hit, 0.5f);

        if(hit.collider != null)
        {
            isGrounded = true;
        }
        else
        {
            isGrounded = false;
        }

        //Move our player left or right if the absolute value of touchDelta.x value is greater than the absolute value of touchDelta.y
        if (Mathf.Abs(touchDelta.x) > Mathf.Abs(touchDelta.y))
        {
            rb.AddForce(Vector3.right * touchDelta.x * speed, ForceMode.Impulse);
        }
       
        rb.AddForce(Vector3.down * downForce, ForceMode.Force);
        if (jump)
        {
            rb.AddForce(Vector3.up * jumpHight, ForceMode.Impulse);
        }

    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Obstacle"))
        {
            GameManager.GameOver();
        }
    }
}

Only thing I can think of is that your jump is probably executing over multiple FixedUpdate calls.

Probably want to ensure that your jump code only happens for one FixedUpdate frame. You should reset your jump flag in the actual jump itself, too:

if (jump)
{
    rb.AddForce(Vector3.up * jumpHight, ForceMode.Impulse);
    jump = false;
}
1 Like

Just tried this. It still doesn’t work.

Please put more effort into debugging, rather than just ‘tried this, doesn’t work’.

Did you ensure your tap input isn’t triggering across multiple frames?

How would I check for this?

Same way the rest of us debug code, usually by either using Debug.Log to see how their code is executing, or attaching the debugger.

In this situations, using some calls to Debug.Log will immediately answer this question for you.

Ok, I added a Debug.log right before the rb.AddForce is called for the jump.
The number of times it is printed in the console varies from one to four times per jump.

Well there you go. You know now the issue is that your jump input is triggering for more than one frame.

Now go investigate why that’s happening, and fix that.

1 Like

Alright! Fixed the issue.
I needed to set isGrounded to false after jump was called just so the conditions for jumping couldnt be met in the few frames that my raycast detected ground.

2 Likes