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 EasingCurve enum 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 how t is mapped before easing.

  • Extension methods to evaluate curves efficiently in Unity.

  • Optional conversion to DOTween’s Ease enum (if DOTWEEN scripting define is present).


Curves

Curves include Linear, and all In-Out of this graph.


Loop Mode

Mode
Description
Default

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