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);
Or we could “ease in” with coserp:
t = 1f - Mathf.Cos(t * Mathf.PI * 0.5f)
We could even create exponential movement:
t = t*t
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)
Or my personal favorite, “smootherstep”:
t = t*t*t * (t * (6f*t - 15f) + 10f)
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.
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.
miss.ingstring said:
Insightful and clear article. Thank you.
Juan Pablo Correa said:
After reading this I really feel like a pro, awesome stuff, Thanks..
Fabien F. said:
Thanks for this article, i used it with an animationCurve, it works like a charm !
Emisael Carrera said:
I fucking love you now! lol Awesome! Beautiful…
Attila said:
wonderfully explained…thanks!
Andre said:
Not enough words to thank you for such a valuable article. Have a great 2015!
devGolem said:
+1 😀 Very well done!
piotr said:
Really great post. Thank you
Elton Maiyo said:
This also achieves a similar effect:
transform.position = Vector3.Lerp(startPos, endPos, Time.time);
James said:
This was incredibly helpful, thank you!
jmiles42 said:
Reblogged this on Jordan Miles's 3D Life and commented:
Very helpful for a designer like me.
Frederik frydenberg said:
Very cool post. Just a small note. The lerp parameter t is not really a percentage, but a value between 0 and 1.
Daniel said:
I love you for the smoother step. It’s brilliant and really something unity should have on bord. Thanks for the great article on such a “tiny” topic. Cheers
Jos said:
It is broken for me somehow. If I put in a number, such as .05f, for the ‘t’ parameter in the Vector3.Lerp function, it still performs a lerp, instead of it sticking in the position where it would be 5% of the way. filling in for example .4 would make it go quicker than .05f. So this whole function doesn’t work for me sadly, any idea why?
Jos said:
Still very nicely written tho!
Jos said:
NVM! I fixed the issue already, my posts may be deleted!!
Wajeeh Hassan said:
You. Are. AWESOME.
Thanks alot!
joan said:
A lot of thanks for this article!
Álvaro Saavedra said:
Thanks man, really awesome!
visulth said:
Thanks for writing this article Robert, seeing how to apply these functions via lerp is really fascinating.
I do have one question if you don’t mind (I hope you still check this blog). The lerps use time as the variable that controls the output of the LERP. What if you wanted to use some other relationship?
For example, I was thinking of using it for a stamina bar that decays at first slowly then quickly, using coserp, based on what the player’s current stamina is (e.g. at max stamina you decay slowly, at near-empty you burn through very quickly). But I can’t figure out how to get the LERP to use the relationship of currentStamina/maxStamina as opposed to currentLerpTime/LerpTime (using time results in stamina that drains slowly then quickly, but without any regard to whether you’re at the final 10% of your stamina or the first 90%).
I wonder if I’m approaching it from the wrong side, since I’m basically piping in the LERP’s result into its own “t” variable, as opposed to the examples you made which have two distinct variables (“t” and then the “result” of the LERP).
Any suggestions would be greatly helpful.
Petr Havlát said:
Great Article! Please can I ask u something. Im just stuck in problem i cant figure out. when i lerp these 2 vars start = transform.position -> end.x = random num, end.y = 0, end.z = random num. end.y after lerp is always negative. it really sucks cuz i m trying to make board game. so stones always hide.. can u be so kind and give me advice or at least a clue how to solve it. thanks
giseudo said:
Great article! Thanks!!
Mirko Katzula said:
Thank you! Very helpful!!! It took some attempts to get the timing concept of LERP…
LerpSucks said:
Lerp is broken if it requires 30-40 lines of code.