Search code examples
unity-game-enginemathlerp

Mathf.Lerp misses final value


I'm using Mathf.Lerp to animate a circular progress bar.

The progress bar is an image that has a property named "FillAmount":

0 means not filled
1 means filled

Along with the Fill property "Radial 360", it acts somewhat like a clock.

For some reason, the final value after executing the Lerp function is missed, and I don't see how I could improve the function to end up with the expected result.

My circular progress bar is a 3/4 ring, that's why I multiply the uFrom and uTo (which are between 0 and 100) by 0.75.

This is my code:

public IEnumerator AnimateHealthChange(int uFrom, int uTo)
{
    float fFade = 0.25f;

    float fOld = ((uFrom * 0.75f) / 100);
    float fNew = ((uTo * 0.75f) / 100);

    Debug.Log("Changing from " + fOld.ToString() + " to " + fNew.ToString());

    for (float t = 0.01f; t < fFade; t += Time.deltaTime)
    {
        ImageHealthRing.fillAmount = Mathf.Lerp(fOld, fNew, Mathf.Min(1, t / fFade));
        yield return null;//
    }

    Debug.Log("Final filling after for-next-statement: " + ImageHealthRing.fillAmount.ToString());

    yield return null;
}

For example, when I call AnimateHealthChange(0, 100), I get the following log outputs:

Changing from 0 to 0.75

Final filling after for-next-statement: 0.6807814

I expected the final result to 0.75, not 0.6807814.

Does anybody see my mistake, or did I use Lerp incorrectly?

enter image description here


Solution

  • Your for loop is a problem since it makes no guarantees:

    for (float t = 0.01f; t < fFade; t += Time.deltaTime)
    

    Time.deltaTime can be any number (depending on the performance of the game and the framerate) and you're using t < fFade instead of t <= fFade so even if you do, by chance, end up with t == fFade, (which is the value it should end on to fill properly) it's going to skip that iteration of the loop.

    The easiest solution here would be to just add an extra statement after the loop that forces fillAmount to the proper value:

        ...
        yield return null;//
    }
    ImageHealthRing.fillAmount = fNew;
    

    This way your loop is still handling the animation and when your function ends, you're guaranteeing that it's ending on the proper value.