Interpolation & Easing
Interpolation means finding a value between two others. It's behind almost every smooth motion in games: doors opening, cameras following, health bars draining, colors fading. This guide covers the four main ways to do it, which to pick, and how to make it feel good with easing.
A dimmer switch between "off" (A) and "full brightness" (B). The knob position is t: 0 is fully A, 1 is fully B, 0.5 is halfway. Interpolation is just "set the knob to t, what's the brightness?" Easing is changing how fast you turn the knob: slowly at first, then faster, then slowly again.
Lerp: the core idea
Lerp (linear interpolation) is simply: a + (b - a) * t.
Mathf.Lerp(0f, 10f, 0.25f); // 2.5
Vector3.Lerp(pointA, pointB, 0.5f); // halfway between the points
Color.Lerp(Color.white, Color.red, 0.8f); // mostly red
Quaternion.Slerp(rotA, rotB, 0.5f); // halfway rotation
Lerp clamps t to 0-1. LerpUnclamped doesn't, so t = 1.2 overshoots past b (useful for bouncy effects).
The four patterns
| Pattern | Feels like | Arrives? | Use for | Tool |
|---|---|---|---|---|
| 1. Fixed duration | "Takes exactly 0.5 s" | Yes, on time | Doors, UI transitions, cutscenes | Lerp with t = elapsed / duration |
| 2. Constant speed | "Moves at 5 m/s" | Yes, exactly | Enemies walking, turrets turning | MoveTowards, RotateTowards |
| 3. Smooth follow | "Fast when far, slows as it gets close" | Almost (gets very close) | Cameras, UI following a target, smoothing input | Lerp with a decay factor |
| 4. Spring | "Has momentum, eases in and out" | Yes (approximately) | Cameras, zoom, aim smoothing | SmoothDamp |
1. Fixed duration (with easing)
using System.Collections;
using UnityEngine;
public class SlidingDoor : MonoBehaviour
{
[SerializeField] private Vector3 openOffset = new Vector3(2f, 0f, 0f);
[SerializeField] private float duration = 0.6f;
[SerializeField] private AnimationCurve ease = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
public IEnumerator Open()
{
Vector3 start = transform.position;
Vector3 end = start + openOffset;
for (float elapsed = 0f; elapsed < duration; elapsed += Time.deltaTime)
{
float t = elapsed / duration; // 0 -> 1 over "duration" seconds
transform.position = Vector3.Lerp(start, end, ease.Evaluate(t));
yield return null;
}
transform.position = end; // make sure we land exactly
}
}
start and end are stored once. Only t changes. This is what DOTween does for you in one line.
2. Constant speed
// Never overshoots, arrives exactly, same speed on every machine
transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRot, degreesPerSec * Time.deltaTime);
health = Mathf.MoveTowards(health, targetHealth, drainPerSec * Time.deltaTime); // draining bar
3. Smooth follow (frame-rate independent)
You'll often see this in tutorials:
// Common, but frame-rate DEPENDENT: faster at high FPS
transform.position = Vector3.Lerp(transform.position, target.position, 0.1f);
It moves 10% of the remaining distance per frame, so 144 FPS catches up much faster than 30 FPS. Multiplying by Time.deltaTime (0.1f * Time.deltaTime) is closer but still not exact. The correct version uses an exponential:
[SerializeField] private float sharpness = 10f; // higher = snappier
void LateUpdate()
{
// Same feel at any frame rate
float t = 1f - Mathf.Exp(-sharpness * Time.deltaTime);
transform.position = Vector3.Lerp(transform.position, target.position, t);
}
4. SmoothDamp (spring)
private Vector3 velocity; // SmoothDamp stores its speed here between frames
void LateUpdate()
{
transform.position = Vector3.SmoothDamp(transform.position, target.position, ref velocity, 0.25f);
// 0.25 = roughly how long it takes to reach the target
}
Unlike pattern 3, SmoothDamp accelerates from rest too, so it feels heavier and more natural. Great for cameras. Also available as Mathf.SmoothDamp and Mathf.SmoothDampAngle.
Easing
Easing reshapes t before you use it. t still goes 0 → 1, but not in a straight line.
public static class Ease
{
public static float InQuad(float t) => t * t; // slow start
public static float OutQuad(float t) => 1f - (1f - t) * (1f - t); // slow end
public static float InOutQuad(float t) => t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
public static float OutCubic(float t) => 1f - Mathf.Pow(1f - t, 3f); // strong slow end
public static float SmoothStep(float t)=> t * t * (3f - 2f * t); // gentle in and out
public static float OutBack(float t) // overshoots slightly, then settles
{
const float c1 = 1.70158f, c3 = c1 + 1f;
return 1f + c3 * Mathf.Pow(t - 1f, 3f) + c1 * Mathf.Pow(t - 1f, 2f);
}
}
// Usage (use LerpUnclamped for OutBack so the overshoot isn't cut off)
transform.localScale = Vector3.LerpUnclamped(Vector3.zero, Vector3.one, Ease.OutBack(t));
t: the horizontal axis is time, the vertical axis is how far along the value is. The faint line marks the target (1); steep means fast. OutBack briefly goes past the target, then settles.| Ease | Feels | Good for |
|---|---|---|
| Linear | Mechanical | Rotating fans, progress bars, conveyor belts |
| Out (Quad/Cubic) | Fast then gentle stop | Most UI and things arriving: the default choice |
| In | Slow then fast exit | Things leaving the screen, falling |
| InOut / SmoothStep | Gentle both ends | Camera moves, doors, elevators |
| OutBack | Playful overshoot | Pop-ups, pickups, buttons |
AnimationCurve fields let designers draw their own easing in the Inspector: [SerializeField] AnimationCurve curve; then curve.Evaluate(t).
Other helpers
InverseLerp and remapping
// "Where is value between a and b?" -> 0..1
float t = Mathf.InverseLerp(10f, 50f, speed); // speed 30 -> 0.5
// Remap one range to another: speed 10..50 -> engine pitch 0.8..2.0
float pitch = Mathf.Lerp(0.8f, 2f, Mathf.InverseLerp(10f, 50f, speed));
Angles and rotations
Mathf.Lerp(350f, 10f, 0.5f); // 180: the LONG way around
Mathf.LerpAngle(350f, 10f, 0.5f); // 0: the short way. Use this for angles.
Quaternion.Slerp(a, b, t); // spherical: constant angular speed, best for rotations
Quaternion.Lerp(a, b, t); // slightly faster, slightly uneven speed for big angles
Vector3.Slerp(dirA, dirB, t);// rotate a DIRECTION, keeping its length
What happens at runtime
- All these functions are small pieces of math. They're very cheap and create no garbage.
- The "smoothness" comes from calling them every frame with a
tthat changes. Pattern 1 changestover time; patterns 2-4 move the current value a bit each frame. - Anything multiplied by
Time.deltaTime, or driven by elapsed time, stops whenTime.timeScale = 0. UseTime.unscaledDeltaTimefor pause-menu animations.
Common Mistakes
Lerp(a, b, Time.time):Time.timeis seconds since start, so after 1 second it's stuck atb. Use elapsed time divided by duration.- Lerping from the current value with a constant t and expecting a fixed duration. It never truly arrives and speed depends on FPS. Use pattern 1 or 3 properly.
- Lerping Euler angles. Use
Quaternion.SlerporMathf.LerpAngle. - Not snapping at the end of a fixed-duration lerp. The last frame may land at 0.98. Set the final value after the loop.
See also
- Bezier Curves (interpolating along a curve)
- Mathf, Vector3, Quaternion
- DOTween (easing built in)
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Unity documentation.