Lerping with Coroutines and Animation Curves

Written by rhysp | Published 2017/06/15
Tech Story Tags: indiedev | unity3d | indie-game-dev | gamedev | animation

TLDRvia the TL;DR App

Lerp (Linear Interpolation) methods provide the intermediary steps of a transition. They are most commonly used in Unity for animation of vectors and quaternions with Vector3.Lerp() and Quaternion.Lerp() respectively, along with the flexible Mathf.Lerp() for floats.

Robert Utter wrote a highly recommended explanation of the proper way to use these methods in Lerp Like a Pro. This article intends to extend on this knowledge

Linear, ease-in, and custom wave interpolation

Isolating animation within a coroutine

The simplest implementation of coroutines within Unity allows components to trigger a function that will run every subsequent frame, similar to the Update() method found in all MonoBehaviour scripts. Let’s prepare an IEnumerator that would do just that.

<a href="https://medium.com/media/462466cee1ff76174d1d3ffb90062919/href">https://medium.com/media/462466cee1ff76174d1d3ffb90062919/href</a>

Here, yield return null simply tells Unity to continue from this line on the next frame. It is common to place yield methods within loops to benefit from continuous iteration. Unfortunately in our current script we aren’t doing anything in the loop, plus, there is never a condition that would prevent the loop from running forever!

<a href="https://medium.com/media/cdf0926a929b5b8f43c92cdb0389836a/href">https://medium.com/media/cdf0926a929b5b8f43c92cdb0389836a/href</a>

If we add a duration value, we now know the destination for this animation. At every frame, we increment our journey value, which represents the current progress. Printing a division of the two provides us with the percentage through the animation’s journey each frame.

Currently, this coroutine would still run infinitely, printing endless output of 1 once the clamped journey reaches and exceeds the total duration.

<a href="https://medium.com/media/4130ca1153a4db03e886b85d3c84caeb/href">https://medium.com/media/4130ca1153a4db03e886b85d3c84caeb/href</a>

By moving our Debug calculation into a variable, we can now easily update our iterate flag against it’s value. Now, the while loop will exit once iterate == false, concluding the coroutine.

A common alternative iteration could be while ((journey / duration) <= 1f). This will mean that the final coroutine frame is very unlikely to run precisely on the completed journey and would require additional “cleanup” after the loop.

The iterate flag alleviates this by ensuring that the final frame’s interpolations are always against the 100% journey progress, while still allowing any post-loop scripting, as needed.

But, the coroutine still doesn’t do anything.

<a href="https://medium.com/media/1664ed909921f5ca7c10af7eecc86ee7/href">https://medium.com/media/1664ed909921f5ca7c10af7eecc86ee7/href</a>

Now we’re cookin’! Two Vector3 parameters are defined, which we provide to Vector3.Lerp with percent to interpolate between the two points. Naturally we need to be able to run this as a coroutine, and luckily our flexible parameters can provide a handy example such as:

<a href="https://medium.com/media/a543b792c56210e83fc0d473297f503a/href">https://medium.com/media/a543b792c56210e83fc0d473297f503a/href</a>

And there we have it. A simple, flexibly and isolated animation method for an object’s position. The reusable flow of journey, percent and iterate make duration-based animation consistent and precise for coroutines.

Non-linear interpolation (Nlerp!?)

We have animation, excellent! Our position can move 10 units in 10 seconds, moving 1 unit per second…. uhhhh, kinda boring.

Linear interpolation, by it’s definition, is straight forward. Games and interface visuals demand to be juicy, everybody wants to see realistic and satisfying animations.

There are excellent libraries, such as DOTween and Mathfx, that can assist with common animation curves. Alternatively, we can take advantage of an Inspector-friendly AnimationCurve to visually define our animation flow throughout the duration:

<a href="https://medium.com/media/7acb4aa9758063bc08d5e3b13c657c11/href">https://medium.com/media/7acb4aa9758063bc08d5e3b13c657c11/href</a>

By defining curvePercent, against a public AnimationCurve in our component, we can now use the curve to represent the variation of the interpolation for faster take off, intermittent jitters, tapered slow down or anything else we can imagine! Simply ensure that the curve begins at (0,0) (time and value), and ends at (1,1).

Tip: By also changing Lerp to LerpUnclamped, our curves can go beyond the bounds of the origin and target positions. This can create elaborate “bouncy” effects.

When editing an Animation Curve, be sure the start and end points go from (0,0) to (1,1). The linear preset is a perfect template to begin adding and modifying keys

Best of all, all curves will adapt to the duration provided by your coroutine argument, adding even more flexibility and re-usability.

Serialised curves

Unity’s visual editor is sufficient to design curves, along with saving libraries of curves for reuse or to package with a project, but our scripts can also define curve defaults or presents directly.

<a href="https://medium.com/media/0985349432be580ebedd1330224a0669/href">https://medium.com/media/0985349432be580ebedd1330224a0669/href</a>

Curves can be declared with any number of keyframes to represent the desired path. The examples above match two core presets from the curve editor defaults.

Tip: It can be useful to declare one of the above default curves in scripts to avoid confusion. If a script’s curve is never drawn in the visual editor, no animation will occur on that object


Published by HackerNoon on 2017/06/15