We ended the previous post with a complete implementation of AnimationHelper, a singleton-like object to aid in creating framerate-independent lerp animations quickly and easily. Since linear animations can get boring pretty quickly, this post will demonstrate how to support easing functions, well, “easily.”

Easing functions

Easing is a way to transform lerp animations so that they’re not so darn “linear.” For example, say you wanted to animate a hovering ship taking off. Rather than keeping the speed constant throughout the animation, it would look more realistic if the ship eased out of its stationary position. (A side note on semantics: by “ease out,” I’m referring to the beginning of an animation, and by “ease in,” I’m referring to the end of an animation, though you’ll often come across definitions that are reversed from this).

What we need is a way to transform our t value so that it progresses more slowly in the beginning of the animation. Take a look at the following “ease out” function (computed using Wolfram|Alpha):

Eased out <em>t</em> vs. original <em>t</em>

Easing with AnimationHelper

The previous function is a plot of t2. We could achieve the desired effect in an AnimationHelper animation simply by modifying the t value we pass into the Lerp call:

AnimationHelper.Animate(Time.time, 3.0f, (t) => {
    transform.localPosition = Vector3.Lerp(Vector3.zero, 100f * Vector3.one, t * t);
});

Finally, to facilitate multiple easing functions, we can create a static class to hold their various implementations. In this case, we’ll define easing in, easing out, and easing on both ends (note that this script does not need to be attached to a game object, since it’s not a Monobehaviour):

using UnityEngine;
using UnityEngine.Assertions;

public static class EasingFunctions
{
    public static float easeIn(float t, float e = 2f)
    {
        Assert.IsTrue(e >= 1f);
        return 1f - Mathf.Pow(1f - t, e);
    }

    public static float easeOut(float t, float e = 2f)
    {
        Assert.IsTrue(e >= 1f);
        return Mathf.Pow(t, e);
    }

    public static float easeInOut(float t, float e = 2f)
    {
        Assert.IsTrue(e >= 1f);
        return Mathf.Pow(t, e) / (Mathf.Pow(t, e) + Mathf.Pow(1f - t, e));
    }
}

Then, the previous animation can become:

AnimationHelper.Animate(Time.time, 3.0f, (t) => {
    transform.localPosition = Vector3.Lerp(Vector3.zero, 100f * Vector3.one, EasingFunctions.easeOut(t));
});

To wrap up this section, a couple of things should be noted. First, the easing functions defined above take exponents. An e value of 1 will result in no easing, while increasing values of e will result in more pronounced easing. Lastly, it is important that these easing functions are 0 and 1 when t = 0 and 1, respectively. This ensures that, despite any easing, the original animated object will start and stop where expected. This is especially important to consider if you’d like to add more custom easing functions (a springy overshooting easing function, for example).

Next post

The final post in this series will add one finishing touch to the implementation of AnimationHelper, namely animation “tokens” to help you avoid potentially-conflicting animations.


unity animation coroutine easing lerp
PREV NEXT