Why ForceMode.VelocityChange is inconsistent and how to fix it?

So i’ve created a simple test project with nothing in there but a camera, light and two cubes.
I did not alter any physics settings (i did not alter any settings if that matters).

Both cubes have Rigidbodies attached. For cube number one it’s almost default, only isKinematic is set to true, so it doesn’t move. It acts like a ground.

Here’s the cube number two:


(Mass = 40; Drag = 1) - everything else - default;


As you can see, there is also “Jump” script attached to the second Cube. It’s really simple:

public class jump : MonoBehaviour {

	Rigidbody rb;
	public float jumpHeight = 10;

	void Awake()
	{
		rb = GetComponent<Rigidbody>();
	}

	void FixedUpdate()
	{
		if(Input.GetButtonDown("Jump"))
		{
			rb.AddRelativeForce(Vector3.up * jumpHeight, ForceMode.VelocityChange);
		}
	}
}

So, the problem is: every time i press “Space” i get kind of random jump height. Of course i used google to find a fix, but it got me nothing useful, in fact, all i got is complains about the same issue dated back to the year 2011. Any help will be appreciated.

Worth mentioning that i have to add force relatively to the object, hence the “AddRelativeForce”.

You said you needed it, I just wanted to clarify (for a jump solution, anyway). Because AddRelativeForce is in relative position to the object, my understanding is that typically, unless you’re tinkering with variable “world up” directions, AddForce(Vector3.up) is what you want, because otherwise its getting the transform objects up rotation (which can be a problem if you spin at all).

Beyond that, if you just want a raw Velocity change, you can just do:

rb.velocity = rb.velocity + (transform.up * jumpHeight);

This is relative to the transform object, but if you don’t need it relative, you can use Vector3.up instead of transform.up.

Or experiment with ForceMode.Impulse instead (my personal preference, as I like the impulse curve).

The problem was that FixedUpdate sometimes can run multiple times in one frame, causing “GetButtonDown” being registered also multiple times, hence the double jump height.
So, one solution would be to check input in Update method and in FixedUpdate only do the jumping itself:
public float jumpHeight = 10;

    bool hasJumped;
    Rigidbody rb;
 
    void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }
 
    void Update()
    {
        if(Input.GetButtonDown("Jump"))
        {
            hasJumped = true;
        }
    }
 
    void FixedUpdate()
    {
        if(hasJumped)
        {
            rb.AddRelativeForce(Vector3.up * jumpHeight, ForceMode.VelocityChange);
            hasJumped = false;
        }
    }

Here is the problem though: Update get’s called after FixedUpdate, so your jump can sometimes start 1 frame later. It’s not a big problem, but if you want it to be perfect - you need to check if FixedUpdate was called on that frame with Time.frameCount, so the Jump will occur exactly once and exactly in the frame when button was pressed, like this:

    public class jump : MonoBehaviour {
 
    Rigidbody rb;
    int jumpFrame;
 
    public float jumpHeight = 10;
 
 
    void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }
 
    void FixedUpdate()
    {
 
        if(Input.GetButtonDown("Jump"))
        {
            Jump();
        }
    }
 
    void Jump()
    {
        if(Time.frameCount != jumpFrame)
        {
            jumpFrame = Time.frameCount;
            rb.AddRelativeForce(Vector3.up * jumpHeight, ForceMode.VelocityChange);  
        }
     
    }
}