A lot of Unity 3D tutorials online use Unity’s linear interpolation incorrectly, including the official video tutorials(!). However, it’s actually very easy to use, once you understand how it works.
The prototype for Vector3.Lerp
looks like this
static Vector3 Lerp(Vector3 start, Vector3 finish, float percentage)
What it does is simple: it returns a point between start and finish, based on the value of percentage
:
- At
percentage = 0.0
,Lerp()
returnsstart
- At
percentage = 1.0
,Lerp()
returnsfinish
- At
0.0 < percentage < 1.0
,Lerp()
returns a point betweenstart
andfinish
. - So at
percentage = 0.5
, it returns the point exactly halfway betweenstart
andfinish
. - And at
percentage = 0.10
, it returns a point very near tostart
etc.
This explains why it’s called linear interpolation: It moves smoothly (interpolates) at a constant speed (linearly) between two points!
How to use Lerp correctly
To use Lerp()
correctly, you simply have to make sure that you pass the same start
and finish
values every frame while moving percentage up from 0.0 to 1.0 each frame.
Here’s an example. When the spacebar is pressed, we’ll lerp our object 10 spaces forward over a period of 1 second.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using UnityEngine; using System.Collections; public class LerpOnSpacebarScript : MonoBehaviour { /// <summary> /// The time taken to move from the start to finish positions /// </summary> public float timeTakenDuringLerp = 1f; /// <summary> /// How far the object should move when 'space' is pressed /// </summary> public float distanceToMove = 10; //Whether we are currently interpolating or not private bool _isLerping; //The start and finish positions for the interpolation private Vector3 _startPosition; private Vector3 _endPosition; //The Time.time value when we started the interpolation private float _timeStartedLerping; /// <summary> /// Called to begin the linear interpolation /// </summary> void StartLerping() { _isLerping = true; _timeStartedLerping = Time.time; //We set the start position to the current position, and the finish to 10 spaces in the 'forward' direction _startPosition = transform.position; _endPosition = transform.position + Vector3.forward*distanceToMove; } void Update() { //When the user hits the spacebar, we start lerping if(Input.GetKey(KeyCode.Space)) { StartLerping(); } } //We do the actual interpolation in FixedUpdate(), since we're dealing with a rigidbody void FixedUpdate() { if(_isLerping) { //We want percentage = 0.0 when Time.time = _timeStartedLerping //and percentage = 1.0 when Time.time = _timeStartedLerping + timeTakenDuringLerp //In other words, we want to know what percentage of "timeTakenDuringLerp" the value //"Time.time - _timeStartedLerping" is. float timeSinceStarted = Time.time - _timeStartedLerping; float percentageComplete = timeSinceStarted / timeTakenDuringLerp; //Perform the actual lerping. Notice that the first two parameters will always be the same //throughout a single lerp-processs (ie. they won't change until we hit the space-bar again //to start another lerp) transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete); //When we've completed the lerp, we set _isLerping to false if(percentageComplete >= 1.0f) { _isLerping = false; } } } } |
How to use Lerp incorrectly
In our above example, we call Vector3.Lerp()
like this:
transform.position = Vector3.Lerp(_startPosition, _endPosition, percentageComplete);
However, the method for calling Lerp you see in many tutorials online looks like this:
transform.position = Vector3.Lerp(transform.position, _endPosition, speed*Time.deltaTime);
Before I explain what’s wrong with this, please take a moment and try to figure out for yourself what this code will do, and why it’s wrong
…
…
…Ready?
The issue with the above code is that the first parameter to Lerp()
, transform.position
, changes every frame! Additionally, the percentage
parameter (the third parameter) does not increase from 0.0 to 1.0, but instead is set to the completely arbitrary value speed*Time.deltaTime
.
What this code actually does is move the object speed*Time.deltaTime
-percent closer to _endPosition
every frame.
There are several problems with this.
- It’s non-linear ie. the speed is not constant. This is easy to see in the official video tutorial; the object moves quickly at first, and slows down as it nears its destination
- The object’s speed varies with the user’s framerate. The higher the user’s framerate, the faster the object will move. This defeats the purpose of using
FixedUpdate()
to begin with, which is supposed to alleviate this problem. - The object never reaches its destination since we only move a percentage closer each frame. Actually, due to floating-point rounding, it may or may not ever reach its destination. Additionally, because of problem 2, whether or not it ever reaches its destination depends on the user’s framerate. Ouch!
The implementation of Lerp
As a final note, I’d like to share how Lerp()
is typically implemented. This section is not required, but mathematically-inclined readers will find it interesting.
Implementing Lerp()
is actually remarkably simple:
1 2 3 4 5 6 7 8 9 10 11 | public static Vector3 Lerp(Vector3 start, Vector3 finish, float percentage) { //Make sure percentage is in the range [0.0, 1.0] percentage = Mathf.Clamp01(percentage); //(finish-start) is the Vector3 drawn between 'start' and 'finish' Vector3 startToFinish = finish - start; //Multiply it by percentage and set its origin to 'start' return start + startToFinish * percentage; } |
(Edit Oct 2016: A commenter correctly pointed out that this implementation of Lerp()
has floating-point rounding issues. See his comment for a more accurate version)