Tags

,

I see this sort of thing far too often:

transform.position = Vector3.Lerp(startPos, endPos, Time.deltaTime);

The person posting it is usually convinced that Vector3.Lerp is “broken”, but the real problem is that they’re not using it correctly.

Lerp, short for “linear interpolation” does one very simple thing: given two values, x and y, it returns a value that is t percent between them. If you expect the output to change, the arguments you pass in need to reflect that!

In the example above, it doesn’t make sense to just pass in Time.deltaTime, because that’s only the time that passed during the most recent frame. If your game is running at a constant 50fps, that’s always going to be 0.02.

We’ll get better results if we let that timer accumulate over multiple frames.

Something like this should get the point across:

    public class LerpExample : MonoBehaviour {
        float lerpTime = 1f;
        float currentLerpTime;

        float moveDistance = 10f;

        Vector3 startPos;
        Vector3 endPos;

        protected void Start() {
            startPos = transform.position;
            endPos = transform.position + transform.up * moveDistance;
        }

        protected void Update() {
            //reset when we press spacebar
            if (Input.GetKeyDown(KeyCode.Space)) {
                currentLerpTime = 0f;
            }

            //increment timer once per frame
            currentLerpTime += Time.deltaTime;
            if (currentLerpTime > lerpTime) {
                currentLerpTime = lerpTime;
            }

            //lerp!
            float perc = currentLerpTime / lerpTime;
            transform.position = Vector3.Lerp(startPos, endPos, perc);
        }
    }

You could create an empty scene, drop in a cube, attach this script, and see it in action.

The variable perc is used where Time.deltaTime used to be. What is perc? It’s a number between 0 and 1, representing our progress through the Lerp.

If you’re curious, check the result if you change lerpTime or moveDistance.

Lerp everything

Unity provides helper functions for interpolating many different values.

  • Mathf.Lerp
  • Vector3.Lerp
  • Color.Lerp

At the end of the day, though, Mathf.Lerp is all you need; the others are just convenience methods on top of that. It’s easy enough to write your own Lerp function for any class or data you might need to interpolate.

In the specific case of quaternions, which Unity uses to represent rotation, there’s even a Slerp or “Spherical Lerp”.

Any time you have two values, and you’d like to smoothly transition between them? Interpolation is your friend!

Beyond Lerp

What if we passed Lerp a t that wasn’t just a straight percentage? What if, instead of a straight line graphed between two points, we wanted some sort of curve? Our input must remain between 0 and 1, but there are many functions that accomplish that.

For example, we could “ease out” with sinerp:

float t = currentLerpTime / lerpTime;
t = Mathf.Sin(t * Mathf.PI * 0.5f);

interp-sinerp

Or we could “ease in” with coserp:

t = 1f - Mathf.Cos(t * Mathf.PI * 0.5f)

interp-coserp

We could even create exponential movement:

t = t*t

interp-quad

Multiplying t by itself has an interesting quirk: so long as t is between 0 and 1, multiplying t by itself any number of times will always produce a number that is also between 0 and 1.

Smoothstep

The multiplication property mentioned above is the core concept behind some interpolation methods which ease in and ease out, such as the famous “smoothstep” formula:

t = t*t * (3f - 2f*t)

interp-smooth

Or my personal favorite, “smootherstep”:

t = t*t*t * (t * (6f*t - 15f) + 10f)

interp-smoother

So smooth! (Couldn’t resist.)

Many of these extra formulas I’ve mentioned aren’t included with Unity, but that just gives you the freedom to use any function you can find or create.

Make it breathe

What about a Lerp that never ends?

Robert Yang once said, “Sometimes I wish I could animate every single thing in my game with a sine wave.”

Some games introduce a slight “head bob” while the player is running. Other games need a gentle bob for the main menu camera. I’ve seen lights that “snore”, fading in and out. Whatever the case, I just want to point out how fantastically useful wave functions can be — their input domain is infinite, so they never have to end, and their output range is both narrow and constant, so they’re easy to control.

For example, this script makes an object slowly move up and down, according to a sine wave:

    public class BreatheSimple : MonoBehaviour {
        Vector3 startPos;

        protected void Start() {
            startPos = transform.position;
        }

        protected void Update() {
            float distance = Mathf.Sin(Time.timeSinceLevelLoad);
            transform.position = startPos + Vector3.up * distance;
        }
    }

Adjusting the sine wave gives us more control:

    public class Breathe : MonoBehaviour {
        Vector3 startPos;

        public float amplitude = 10f;
        public float period = 5f;

        protected void Start() {
            startPos = transform.position;
        }

        protected void Update() {
            float theta = Time.timeSinceLevelLoad / period;
            float distance = amplitude * Mathf.Sin(theta);
            transform.position = startPos + Vector3.up * distance;
        }
    }

Amplitude controls how far the object moves; period controls how quickly it does so.

interp-sineparams

A player’s head bob might call for small amplitude and a short period. A menu camera’s slow breathing might call for an extremely long period, or movement on multiple axes at varying rates.

As long as you can sort out the math, you can get whatever behavior you want.

What happens if you take only the absolute value of the sine wave? Upward-only bouncing.

What happens in you clamp the sine wave’s output between 0 and 1? Upward-only bouncing with a pause between bounces. Can you see why? Draw the clamped curve yourself and it should become clear.

What if you clamped that wave’s output between 0.6 and 1? Would you get a sort of “throb” motion? Could that be useful to scale an alert in your UI?

If you want to get really fancy, you could even use an animation curve to manage your Lerp params.

I’ve written some pretty esoteric curve and wave functions. Don’t be afraid to mix it up.

In closing

Lerp is an easy and powerful function with many applications.

One of the first plugins I ever wrote was a camera manager that interpolates between various zooms and positions. I’ve also used interpolation to manage material parameters, UI animation, physical forces, audio volume, simple cutscenes, and more.

This is one of those hammers that turns up more and more often, once you know how to use it.

If you don’t want to write a lot of code, or want a head start, plugins like iTween, GoKit, or Hotween add lots of extra interpolation support to Unity.

Advertisements