Unity3D Cookbook: Lerp

Derek Stobbe
blog.problematic.io
3 min readOct 8, 2016

Let’s take a look at some common problems and anti-patterns when using lerp in Unity, and how to correct them.

Anti-pattern #1: lerping with Time.time

We want to move a GameObject to a target point over time, so we lerp between a starting point and a destination, using Time.time as the t value.

void Update () {
if (Input.GetMouseButton(0)) {
transform.position = Vector3.Lerp(startPosition, targetPosition, Time.time); // this won't work as expected!
}
}

The Problem

As discussed previously, the lerp function produces a value that is some percentage of the distance between two known points, where that percentage is represented by a number in the range of 0 to 1 inclusive. Time.time represents the number of seconds since the start of the game, and quickly becomes greater than 1!

The Solution

Cache the time that the operation started, and use that and the desired duration of movement to calculate how far along you are.

public float duration = 2.5f;
public float startedAt;
void Update () {
if (Input.GetMouseButtonDown(0)) {
startedAt = Time.time;
}
if (Input.GetMouseButton(0)) {
float progress = (Time.time - startedAt) / duration;
transform.position = Vector3.Lerp(startPosition, targetPosition, progress);
}
}
Anti-pattern #2: lerping in placeThis is a similar problem to #1, but can be even more subtle. We want to change some member property in our script from its current value to a target value, so we update the property in place. This is commonly coupled with using Time.deltaTime as the t value (see the next section).void Update () {
transform.position = Vector3.Lerp(transform.position, targetPosition, progress); // this will produce unexpected results
}
The ProblemUnpredictable movement along the interpolated points; reaching the target position too quickly.The SolutionRemember that linear interpolation operates predictably between known, fixed points. Calling lerp will always produce the same value when the same endpoints and t value are used. When you update one of the values in place, you're effectively "sliding" one of the endpoints, which means that calling lerp with the same t value will produce a slightly different result. Done in a 60 fps loop, these errors can add up to produce noticeable effects on gameplay. The solution is, again, caching: in addition to caching when you started lerping, also store the initial value of whatever you're lerping.public float duration = 2.5f;
public float startedAt;
public Vector3 startPosition;
void Update () {
if (Input.GetMouseButtonDown(0)) {
startedAt = Time.time;
startPosition = transform.position;
}
if (Input.GetMouseButton(0)) {
float progress = (Time.time - startedAt) / duration;
transform.position = Vector3.Lerp(startPosition, targetPosition, progress);
}
}
Anti-pattern #3: lerping with Time.deltaTimeUsed along with lerping in place, we again want to move a given value toward a target over time, so we pass Time.deltaTime as our t value.void Update () {
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime); // we may never reach targetPosition using this code!
}
The ProblemThis usage of lerp can produce questions about behavior such as "my object never reaches its destination," or "my game object slows down as it nears its destination." The accumulation of rounding errors can eventually cause the object to "cross the gap" and reach its destination, but there is no guarantee of that, especially if we are relying on exact comparisons (transform.position.Equals(targetPosition)) to determine if we should execute further game logic.The SolutionTime.deltaTime produces small values representing the number of seconds it took the last game frame to run. When the game is running at 60 fps, this number is very small: typically around 1 / 60, or 0.016667 seconds. When passed to lerp as something like lerp(a, b, Time.deltaTime), we are essentially saying "give me the value that is ~1.6% of the distance between a and b". If we weren't updating our lerped value in place, we would see the results jittering ever so slightly near the starting point. The only reason it works at all is, again, because we are "sliding" the starting point gradually toward the destination point.The solution is the same as previous: instead of using Time.deltaTime and updating in place, cache the starting value and timestamp, and calculate progress based on those. If, for whatever reason, you must use Time.deltaTime, you can accumulate it into a variable to accomplish nearly the same effect:public Vector3 startPosition;
public float acc = 0;
void Update () {
if (Input.GetMouseButtonDown(0)) {
startPosition = transform.position;
}
if (Input.GetMouseButton(0)) {
acc += Time.deltaTime;
transform.position = Vector3.Lerp(startPosition, targetPosition, acc);
}
}

--

--