Easing Curve
Curve in Math
Overview
Easing curves map a normalized time parameter t ∈ [0,1] to a smoothed progression y ∈ [0,1]. They are commonly used for animations, UI transitions, and value interpolation where non-linear motion feels more natural.
This package provides:
A comprehensive
EasingCurveenum of classic easing functions (Sine, Quad, Cubic, Quart, Quint, Expo, Circ, Back, Elastic, Bounce) with In/Out/InOut variants and Linear.A loop/wrap system via
LoopMode(Once, Clamp, Loop, PingPong) to control howtis mapped before easing.Extension methods to evaluate curves efficiently in Unity.
Optional conversion to DOTween’s
Easeenum (ifDOTWEENscripting define is present).
Curves
Curves include Linear, and all In-Out of this graph.

Loop Mode
Once
no wrapping; intended for manual control. For numerical safety, values are clamped internally around the easing functions.
Clamp
clamps t into [0,1]. This matches the default behavior of Evaluate(t).
Loop
wraps t to [0,1) repeating.
PingPong
bounces t between 0 and 1.
Usage
Basic interpolation
float duration = 0.5f;
float timer = 0f;
Vector3 start = A, end = B;
EasingCurve curve = EasingCurve.InOutCubic;
void Update() {
if (timer < duration) {
timer += Time.deltaTime;
float t = timer / duration; // 0..1
float eased = curve.Evaluate(t); // default: Clamp
transform.position = Vector3.LerpUnclamped(start, end, eased);
}
}Looping and ping-pong
// Continuous looping
float t = Time.time / period; // can grow without bound
float easedLoop = EasingCurve.OutSine.Evaluate(t, LoopMode.Loop);
// Ping-pong motion
float easedPingPong = EasingCurve.InOutQuad.Evaluate(Time.time, LoopMode.PingPong);Manual time control
// Using Once allows raw t flow; Evaluate will keep values stable around edge cases.
float t = someRawValue; // might be outside [0,1]
float eased = EasingCurve.OutExpo.Evaluate(t, LoopMode.Once);With DOTween
If you use DOTween and define DOTWEEN in Scripting Define Symbols:
csharp
using DG.Tweening;
Ease ease = EasingCurve.InOutBack.ToDoTweenEase();
transform.DOMove(target, 1f).SetEase(ease);Advanced Usage
For high-throughput systems (Burst jobs, particle systems, GPU shaders), it’s often faster to precompute a lookup table (LUT) of an easing curve and then sample it at runtime. Below are reference implementations for:
CPU-side LUT generation (Burst-friendly)
Passing LUT to a shader as a 1D texture
Sampling LUT in C# Jobs
1) Generate a float LUT in C#
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
public static class EasingLutBuilder
{
// Builds a NativeArray<float> LUT in [Allocator] you choose.
// length: number of samples (e.g., 256, 512, 1024)
public static NativeArray<float> BuildLut(EasingCurve curve, int lutSize, Allocator allocator, bool clamp = false)
{
if (lutSize<= 1) lutSize= 2;
var lut = new NativeArray<float>(lutSize, allocator, NativeArrayOptions.UninitializedMemory);
// Sample the curve uniformly over [0,1]
// Index i in [0, length-1] maps to t = i/(length-1)
for (int i = 0; i < lutSize; i++)
{
float t = (float)i / (lutSize- 1);
var b = curve.Evaluate(t, LoopMode.Clamp);
if (clamp) b = math.saturate(b);
lut[i] = b
}
return lut;
}
}Tips:
Choose length as a power of two for cheap modulo/bitmask wrapping.
For extremely tight budgets, quantize to half-floats or bytes (see below).
2) Quantized LUT to byte (0–255)
Reduces memory/bandwidth and is perfect for textures.
public static NativeArray<byte> BuildByteLut(EasingCurve curve, int lutSize, bool clamp = false, Allocator allocator)
{
if (lutSize<= 1) lutSize= 2;
var lut = new NativeArray<byte>(lutSize, allocator, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < lutSize; i++)
{
float t = (float)i / (lutSize- 1);
float y = curve.Evaluate(t, LoopMode.Clamp);.1
if (clamp) b = math.saturate(b);
lut[i] = (byte)math.round(y * 255f);
}
return lut;
}3) Generate LUT to a 1D texture for shaders
Unity doesn’t have true 1D textures, so use a 2D texture with height = 1.
public static Texture2D BuildLutTexture(EasingCurve curve, int lutSize, bool clamp = false, bool linear = true)
{
if (lutSize<= 1) lutSize= 2;
// Create a 1D (height x 1) texture
var tex = new Texture2D(1, lutSize, TextureFormat.R8, mipChain: false, linear)
{
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear
};
// Build a quantized 0..255 LUT directly from the curve
var lutBytes = new byte[lutSize];
for (int i = 0; i < lutSize; i++)
{
float t = (float)i / (lutSize- 1);
float y = curve.Evaluate(t, LoopMode.Clamp);
if (clamp) y = Mathf.Clamp01(y);
lutBytes[i] = (byte)Mathf.RoundToInt(y * 255f);
}
// Upload data
tex.SetPixelData(lutBytes, 0);
tex.Apply(updateMipmaps: false, makeNoLongerReadable: false);
return tex;
}4) Generate LUT with multiple curves to a texture for shaders
public static Texture2D BuildLutTexture(EasingCurve curveX, EasingCurve curveY, EasingCurve curveZ, EasingCurve curveW, int lutSize, bool clamp = false, bool linear = true)
{
if (lutSize<= 1) lutSize= 2;
// Create a 1D (height x 1) texture
var tex = new Texture2D(1, lutSize, format, mipChain: false, linear)
{
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear
};
// Build a quantized 0..255 LUT directly from the curve
var pixels = new Color[lutSize];
for (int i = 0; i < lutSize; i++)
{
float t = (float)i / (lutSize- 1);
float r = curveX.Evaluate(t, LoopMode.Clamp);
float g = curveY.Evaluate(t, LoopMode.Clamp);
float b = curveZ.Evaluate(t, LoopMode.Clamp);
float r = curveW.Evaluate(t, LoopMode.Clamp);
if (clamp)
{
r = Mathf.Clamp01(r);
g = Mathf.Clamp01(g);
b = Mathf.Clamp01(b);
r = Mathf.Clamp01(r);
}
pixels[i] = new Color(r, g, b, r);
}
// Upload data
tex.SetPixelData(lutBytes, 0);
tex.Apply(updateMipmaps: false, makeNoLongerReadable: false);
return tex;
}Last updated