My character loses or gains height inconsistently, how can I fix it?

My concept is that my character should be able to jump to any height it has been, so for example, if my character falls 10 meters then he should be able to jump 10 meters, no more no less. The problem is that sometimes when the character jumps it will lose height, and sometimes it will gain height, and I have no idea why. It usually only gains or loses like 1 meter. Here’s my code:

	public float timer;
	public float vy;
	public float uy;
	public float g;
	public Rigidbody2D rb;
	public float maxHeight;
	public float E;
	public float vE;

	private float ti;
	private float tf;
	private float vx;

	void Awake (){
		maxHeight = rb.position.y;
	}

	void Start () {

	}
		


	void Update () {
		if (rb.position.y > maxHeight) {
			maxHeight = rb.position.y;
		}

	
	

		if (Input.GetKeyDown ("space")) {
			uy = vE;
			ti = tf;
		}

		if (Input.GetKey (KeyCode.Mouse1)) {
			ti = tf;
			uy = 0;
		}

	
	}
	



	void FixedUpdate () {

		timer = tf - ti;

		tf = Time.fixedTime;

		vy = uy + (g * timer);

		rb.velocity = new Vector2 (vx, vy);

		E = (rb.position.y - maxHeight) * rb.mass * g;

		vE = Mathf.Sqrt (2 * (rb.position.y - maxHeight) * g);
	}

There are two answers here, due to having learnt a bit more about the physics engine. The first answer assumes a perfect simulation which Unity is not! It describes how you would find the answer in a maths exam or for the real world, however as Unity physics is applied at fixed intervals rather than continuously it results in some quite significant errors if used in Unity.

The second solution (scroll about half way down and look for the Edit header) is a better solution for mirroring what happens in Unity, it calculates the speed required while taking into account that the acceleration will only be applied in fixed intervals.

If you want the perfect world answer look at The theoretical solution, unfortunately while this would be nice, it’s not normally the one you want unless you are building software to do maths for the user!

If you want the solution that will get you the best results when mimicing Unity look at The unity physics solution.


The theoretical solution

A little hard to follow all the two letter vars, but this is how I’d go about it.

Firstly I’d simplify things by moving all of the calculation into FixedUpdate, and just set a boolean jump in Update.

Next, let’s have a look at the equations of motion:

equations of motion

s is the distance we wish to travel, which we know as the difference between our current position and maxHeight.

u is the starting speed which is what we want to find out

v is the speed at the end, which we know, as at the top of our jump, speed must always be 0 (since as we go up our speed is positive and when we come down our speed is negative).

a is acceleration which in your case is just gravity.

t is the time it takes, we don’t know this information.

So since we don’t know t lets use equation 4 as it doesn’t include it!

v^2 = u^2 + 2as

we want to find out u and we know v is 0, so lets rearrange it to u = sqrt(-2as)

You can’t sqrt a negative number but this works out as we know s is always positive (jumping upwards) and a is always negative (gravity pulls us down) so -2as will always be non-negative.

now we just need to calculate s since we know a is the constant -9.81.

s will be maxHeight minus our currentHeight.

public Rigidbody2D rb;
public float maxHeight;

private float g;
private bool jump = false;

void Awake (){
    maxHeight = rb.position.y;
    g = Physics.gravity.y;
}

void Update () {
    if (rb.position.y > maxHeight) {
        maxHeight = rb.position.y;
    }

    if (Input.GetKeyDown ("space")) {
        jump = true;
    }
}

void FixedUpdate () {
    if(jump){
        float distance = maxHeight - rb.position.y;
        yVelocity = Mathf.Sqrt(-2 * distance * g);
        rb.velocity = new Vector2(0, yVelocity);
        jump = false;
    }
}

Hope that helps!


EDIT

The unity physics solution

This is going to get a bit mathsy but there’s a solution at the end if you don’t care!

So I had a chance to test this out a little and as observed there is a reasonable difference with the planned height and the actual height reached. I am pretty sure this comes from the fact that Unity resolves physics every Time.fixedDeltaTime (usually 0.02) seconds, while the equations above are exact solutions.

I’ve thrown a bit of maths at the problem and I think I have got a solution that appears to be significantly more accurate, basically by copying what I think is happening in Unity’s calculations and taking steps of Time.fixedDeltaTime when working out speeds and accelerations.

using the same variable meanings above as well as n being the number of FixedUpdates passed, and dt being fixedDeltaTime (0.02), we can predict the distance travelled in unity after the number of frames n as:

s = (u + a*dt)*dt + (u + a*dt + a*dt)*dt + ... + (u + n*a*dt)*dt

Since in 1 frame we travel the speed u plus the acceleration over the time a*dt.

We can simplify a bit:

s = n*u*dt + a*dt*dt + sum_i=0->n(i)
s = n*u*dt + a*dt*dt*n*(n+1)/2

Then we need to solve for u:

n*u*dt = s - a*dt*dt*n*(n+1)/2
u = s/(n*dt) - a*dt*(n+1)/2
u = s/(n*dt) - a*(n*dt)/2 - a*dt/2

Now we need to work out the value of n, which is the number of frames over which the jump will reach it’s peak, fortunately we can fall back to equation of motion 5 for this, since we don’t know u yet, but we have all the other values.

s = v*T - a*T*T/2

Remind ourselves that at the top of the jump v is 0 and rearrange for T we get:

T = sqrt(-2*s/a)

another thing to say about T is that it will be equal to the number of frames n multiplied by the frame length dt, so we have T = n*dt, subbing that in we have:

u = s/T - a*T/2 - a*dt/2

so now we know all the values on the right, we can find u!

Solution

public class Jump : MonoBehaviour
{

	public float maxHeight = 5;
	private Rigidbody rb;
	private bool jump = false;

    void Start()
    {
        rb = this.GetComponent<Rigidbody>();
    }

    void Update(){
    	if(Input.GetKeyDown(KeyCode.Space)){
    		jump = true;
    	}

    	if(Mathf.Abs(maxHeight-transform.position.y) < 0.02f){
    		Debug.Log("getting close " + transform.position.y);
    	}
    }

    // Update is called once per frame
    void FixedUpdate()
    {
    	if(jump && maxHeight - transform.position.y > 0){
    		// same as 's' in the maths
    		float distance = maxHeight - transform.position.y;
    		// same as 'a' in the maths, since it is our only acceleration
    		float a = Physics.gravity.y;
    		// same as T in the maths
    		float totalTime = Mathf.Sqrt(-2 * distance/a);
    		float dt = Time.fixedDeltaTime;

    		float u = (distance / totalTime) - (a * totalTime * 0.5f) - (a * dt * 0.5f);
    		Debug.Log(distance);
    		rb.velocity = Vector3.up * u;
    		jump = false;
    	}
    	if(jump){
    		jump = false;
    	}
    }
}