Mathf.Lerp not working as expected

Hi, I am trying to make a really simple task but for some reason it’s not working, I simply want this:

  • When I’m clicking in a certain object, a float goes from it’s current value to 1f, over half a second.
  • When I’m not clicking that one object, the float would go from it’s current value to 0f, also over half a second.

The point of this is to simulate an Axis. For some reason, this isn’t working as I expected:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

	public float Axis;
	public bool pressing = false;

	void Update () {
		if (pressing) {
				Axis = Mathf.Lerp(Axis,1f,0.5f);
		}
		if (!pressing) {
			Axis = Mathf.Lerp(Axis,0f,0.5f);
		}
	}

	void OnMouseDown () {
		pressing = true;
	}

	void OnMouseUp () {
		pressing = false;
	}

	void OnGUI () {
		GUILayout.Label(Axis.ToString());
	}
}

It works fine when I’m pressing, but when I stop pressing, it kinda goes insane and takes alot more time than when I’m pressing and some weird values appear like this: 1.455192E-11 and I fell like that isn’t necessarie since when I’m pressing it always works perfectly, one more thing, should I use Time.deltaTime to make the Lerp exactly 0.5 seconds or does Lerp already does that automatically?

Thanks in advance.

I don’t think lerp works quite how you think it does.

Lerp(float from, float to, float t);

Means we choose the value that is at a ratio t between from and to. So when you are pressing, you are taking the point half way between Axis and 1.0f. So, suppose Axis starts at 0.0f then the sequence of values it will take is:

frame  value
[0]    0.0f
[1]    0.5f; //half way between 0.0 and 1.0f
[2]    0.75f // half way between 0.5 and 1.0f
...

Clearly this is not a linear interpolation. Instead, what you need to do is store the time that as progressed since pressing. Something like this:

float timeSincePress;
float axisStart;

...

void Update()
{
    timeSincePress += Time.deltaTime;
    if(pressing)
    {
        // here we set axis to be between axisStart and 1.0f
        // where timeSinceLastPress is the time since we pressed
        // and 0.5f is the total time we want it to take 
        // (so when time = 0.5, Axis will = 1.0)
        Axis = Mathf.lerp(axisStart, 1.0f, timeSincePress/0.5f)
    }
    ...
}

void OnMouseDown()
{
    pressing = true;
    timeSincePress = 0.0f;
    axisStart = Axis;
}

...

Still some things to fill in there (like what happens if the user holds the mouse down for more than 0.5 seconds? Currently, Axis will go past one, but hopefully that explains lerp a bit better :slight_smile:

Lerp doesn’t do what you think.

What it does is return a value that’s between the first two parameters, using the third parameter to specify how far between.

Axis = Mathf.Lerp(Axis, 1f, 0.5f);

The above usage will set Axis to be half way between what Axis currently is and 1, because you’re passing in 0.5 as the third parameter.

Assuming Axis starts off at zero (and this is currently not reliable because you have not initialised it in your script) then we can look at what will happen to it if we run the above code repeatedly (ie. over a few frames.)

  • 1st frame: Axis changes from 0.0 to 0.5.
  • 2nd frane: Axis changes from 0.5 to 0.75.
  • 3rd frame: Axis changes from 0.75 to 0.875.
  • 4th frame: Axis changes from 0.875 to 0.9375.

And so on.

This demonstrates three things:

1: Using 0.5 does not directly tell anything to happen over a certain amount of time. It controls the speed of change by saying how big a step Axis will change by each time you call lerp. Using 0.5 means step by half the difference between the first two parameters.

2: If you are running at a high frame rate then it will still take the same number of frames to change Axis, but it will happen a lot quicker. This is why you see the use of Time.deltaTime coming into some of the examples.

Axis = Mathf.Lerp(Axis, 1f, 0.5f * Time.deltaTime);

This will make the behaviour frame rate independent by adjusting the third parameter to reflect how long it’s been since the last update. Once you have this setup, you can try using different values in place of the 0.5 to tune the speed of the lerp and get the behaviour that you want.

3: When using lerp like this your Axis value will increase by a smaller and smaller amount as it approaches the second parameter. It’s important to appreciate this because what it means is that it’s not sensible to rely on a lerped value actually reaching the target. It just gets closer and closer, but more and more gradually with each update. This means that you should always check for reaching the top end by using a slightly more tolerant check, as follows…

Axis = Mathf.Lerp(Axis, 1f, 0.5f * Time.deltaTime);

if ((1f - Axis) < 0.01f)
{
    // Got there - well, near enough!
}

The trouble you’re having when you’re not pressing is related to point 3. It is actually lerping towards zero, but the value is getting so small that ToString() has started to represent it in scientific notation. The E-11 in the ouput can be interpreted as “really, really, really tiny”. 1.455192E-11 is just a compact way of writing 0.00000000001455192. Your Axis has shot towards zero very quickly, but it won’t actually ever get there because each time lerp is called, it’s taking a smaller step towards zero than the previous time. In fact, the step is half the size each time because of your use of 0.5. Try running your above code with a value of 0.005 instead of 0.5 and you’ll see in your output that the value of Axis still ends up in scientific notation, but it will take a lot longer because it’s being adjusted by a much smaller step each frame.

Well… I hope this helps. Good luck. :slight_smile: