top of page
Search
  • alexlpc2015

[Unity Coding Designs] #03 - Curve Sampler

Hi and welcome to my Unity Coding Designs Series!

This is a comprehensive series about various coding designs in unity. Some of the scripts are included in my unity utility plugin SKCell. I hope you will find this helpful!


Coding Designs #03 - Curve Sampler

Transitions using animation curves are widely used in games. Preparing your own curve sampler to add some transition effects to your data/objects is very useful!



To use these curves, we need to input an x value, sample that value on the curve to get an output y value. Then, as x varies linearly, y also varies in a certain (nonlinear) pattern.


I will use the curve sampler in SKCell as an example.

First, prepare some enums for difference types of curves.

// Type of curve
public enum CurveType
{
    Linear,
    Quadratic,
    Cubic,
    Quartic,
    Quintic,
    QuadraticDouble,
    CubicDouble,
    QuarticDouble,
    QuinticDouble,
    Sine,
    SineDouble,
    Expo,
    ExpoDouble,
    Elastic,
    ElasticDouble,
    Circ,
    CircDouble,
    Back,
    BackDouble,
    Bounce,
    BounceDouble
}

//In: y varies from 0 to 1
//Out: y varies from 1 to 0
public enum CurveDir
{
    In,
    Out,
}

Then, create a class that represents a curve. Write out all the static presets. (very repetitive, I know!)

public class SKCurve
    {
				//is this curve a preset animation curve?
        public bool isPreset { get; private set; }
		
				//type of the curve
        public CurveType curveType;
				//direction of the curve
        public CurveDir curveDir;

				//presets
        public static SKCurve LinearIn { get { return new SKCurve(CurveType.Linear, CurveDir.In); } }
        public static SKCurve LinearOut { get { return new SKCurve(CurveType.Linear, CurveDir.Out); } }
        public static SKCurve QuadraticIn { get { return new SKCurve(CurveType.Quadratic, CurveDir.In); } }
        public static SKCurve QuadraticOut { get { return new SKCurve(CurveType.Quadratic, CurveDir.Out); } }
        public static SKCurve CubicIn { get { return new SKCurve(CurveType.Cubic, CurveDir.In); } }
        public static SKCurve CubicOut { get { return new SKCurve(CurveType.Cubic, CurveDir.Out); } }
        public static SKCurve QuarticIn { get { return new SKCurve(CurveType.Quartic, CurveDir.In); } }
        public static SKCurve QuarticOut { get { return new SKCurve(CurveType.Quartic, CurveDir.Out); } }
        public static SKCurve QuinticIn { get { return new SKCurve(CurveType.Quintic, CurveDir.In); } }
        public static SKCurve QuinticOut { get { return new SKCurve(CurveType.Quintic, CurveDir.Out); } }
        public static SKCurve QuadraticDoubleIn { get { return new SKCurve(CurveType.QuadraticDouble, CurveDir.In); } }
        public static SKCurve QuadraticDoubleOut { get { return new SKCurve(CurveType.QuadraticDouble, CurveDir.Out); } }
        public static SKCurve CubicDoubleIn { get { return new SKCurve(CurveType.CubicDouble, CurveDir.In); } }
        public static SKCurve CubicDoubleOut { get { return new SKCurve(CurveType.CubicDouble, CurveDir.Out); } }
        public static SKCurve QuarticDoubleIn { get { return new SKCurve(CurveType.QuarticDouble, CurveDir.In); } }
        public static SKCurve QuarticDoubleOut { get { return new SKCurve(CurveType.QuarticDouble, CurveDir.Out); } }
        public static SKCurve QuinticDoubleIn { get { return new SKCurve(CurveType.QuinticDouble, CurveDir.In); } }
        public static SKCurve QuinticDoubleOut { get { return new SKCurve(CurveType.QuinticDouble, CurveDir.Out); } }
        public static SKCurve SineIn { get { return new SKCurve(CurveType.Sine, CurveDir.In); } }
        public static SKCurve SineOut { get { return new SKCurve(CurveType.Sine, CurveDir.Out); } }
        public static SKCurve SineDoubleIn { get { return new SKCurve(CurveType.SineDouble, CurveDir.In); } }
        public static SKCurve SineDoubleOut { get { return new SKCurve(CurveType.SineDouble, CurveDir.Out); } }
        public static SKCurve ExpoIn { get { return new SKCurve(CurveType.Expo, CurveDir.In); } }
        public static SKCurve ExpoOut { get { return new SKCurve(CurveType.Expo, CurveDir.Out); } }
        public static SKCurve ExpoDoubleIn { get { return new SKCurve(CurveType.ExpoDouble, CurveDir.In); } }
        public static SKCurve ExpoDoubleOut { get { return new SKCurve(CurveType.ExpoDouble, CurveDir.Out); } }
        public static SKCurve ElasticIn { get { return new SKCurve(CurveType.Elastic, CurveDir.In); } }
        public static SKCurve ElasticOut { get { return new SKCurve(CurveType.Elastic, CurveDir.Out); } }
        public static SKCurve ElasticDoubleIn { get { return new SKCurve(CurveType.ElasticDouble, CurveDir.In); } }
        public static SKCurve ElasticDoubleOut { get { return new SKCurve(CurveType.ElasticDouble, CurveDir.Out); } }
        public static SKCurve CircIn { get { return new SKCurve(CurveType.Circ, CurveDir.In); } }
        public static SKCurve CircOut { get { return new SKCurve(CurveType.Circ, CurveDir.Out); } }
        public static SKCurve CircDoubleIn { get { return new SKCurve(CurveType.CircDouble, CurveDir.In); } }
        public static SKCurve CircDoubleOut { get { return new SKCurve(CurveType.CircDouble, CurveDir.Out); } }
        public static SKCurve BackIn { get { return new SKCurve(CurveType.Back, CurveDir.In); } }
        public static SKCurve BackOut { get { return new SKCurve(CurveType.Back, CurveDir.Out); } }
        public static SKCurve BackDoubleIn { get { return new SKCurve(CurveType.BackDouble, CurveDir.In); } }
        public static SKCurve BackDoubleOut { get { return new SKCurve(CurveType.BackDouble, CurveDir.Out); } }
        public static SKCurve BounceIn { get { return new SKCurve(CurveType.Bounce, CurveDir.In); } }
        public static SKCurve BounceOut { get { return new SKCurve(CurveType.Bounce, CurveDir.Out); } }
        public static SKCurve BounceDoubleIn { get { return new SKCurve(CurveType.BounceDouble, CurveDir.In); } }
        public static SKCurve BounceDoubleOut { get { return new SKCurve(CurveType.BounceDouble, CurveDir.Out); } }
        
				//let user specify their own curve
				public Func<float, float> func { get; private set; }
				
	      //constructor for preset curves
				public SKCurve(CurveType curveType, CurveDir curveDir)
        {
            this.isPreset = true;
            this.curveType = curveType;
            this.curveDir = curveDir;
        }
				//constructor for customized curves
				//func: takes in x value, returns y value
        public SKCurve(Func<float, float> func)
        {
            this.isPreset = false;
            this.func = func;
        }
    }

Then, create another static class called CurveSampler. We will use this class to sample the curves

public static class SKCurveSampler
{

}

Inside the curve sampler, create a dictionary <CurveType, Func<float, float>> to contain all the functions for preset animation curves.

public static Dictionary<CurveType, Func<float, float>> curveFuncs = new Dictionary<CurveType, Func<float, float>>()
    {
        { CurveType.Linear, (x)=>{ return x; } },
        { CurveType.Quadratic, (x)=>{ return 1 - (1-x)*(1-x); } },
        { CurveType.Cubic, (x)=>{ return 1 - (1-x)*(1-x)*(1-x); } },
        { CurveType.Quartic, (x)=>{ return 1 - (1-x)*(1-x)*(1-x)*(1-x); } },
        { CurveType.Quintic, (x)=>{ return 1 - (1-x)*(1-x)*(1-x)*(1-x)*(1-x); } },
        { CurveType.QuadraticDouble, (x)=>
        {
            return x<0.5f? 2*x*x:2*x*(2-x)-1; }
        },
        { CurveType.CubicDouble, (x)=>
        {
            return x<0.5f? 4*x*x*x:-4*(1-x)*(1-x)*(1-x)+1; }
        },
        { CurveType.QuarticDouble, (x)=>
        {
            return x<0.5f? 8*x*x*x*x:-8*(1-x)*(1-x)*(1-x)*(1-x)+1; }
        },
        { CurveType.QuinticDouble, (x)=>
        {
            return x<0.5f? 16*x*x*x*x*x:-16*(1-x)*(1-x)*(1-x)*(1-x)*(1-x)+1; }
        },
        { CurveType.Sine, (x)=>
        {
            return 0.5f * (1.0f - Mathf.Cos(x * Mathf.PI * 2)); }
        },
        { CurveType.SineDouble, (x)=>
        {
            return 0.5f * (1.0f - Mathf.Cos(x * Mathf.PI)); }
        },
        { CurveType.Expo, (x)=>
        {
            return x==0?0:Mathf.Pow(2.0f, 10.0f * (x - 1.0f)); }
        },
        { CurveType.ExpoDouble, (x)=>
        {
            return x==0?0:(x==1?1:(x<0.5f?0.5f * Mathf.Pow(2.0f,20.0f * x - 10.0f) :0.5f * (2.0f - Mathf.Pow( 2.0f,-20.0f * x + 10.0f)) )); }
        },
        { CurveType.Elastic, (x)=>
        {
            return x==0?0:(x==1?1:(-Mathf.Pow(2.0f,10.0f * x - 10.0f) * Mathf.Sin((3.33f * x - 3.58f) * Mathf.PI * 2))); }
        },
        { CurveType.ElasticDouble, (x)=>
        {
            return x==0?0:(x==1?1:(x<0.5f?-0.5f * Mathf.Pow(2.0f,20.0f * x - 10.0f) * Mathf.Sin((4.45f * x - 2.475f) * Mathf.PI*2):Mathf.Pow(2.0f, -20.0f * x + 10.0f) * Mathf.Sin((4.45f * x - 2.475f) * Mathf.PI*2) * 0.5f + 1.0f)); }
        },
        { CurveType.Circ, (x)=>
        {
            return 1.0f - Mathf.Sqrt(1.0f - x * x); }
        },
        { CurveType.CircDouble, (x)=>
        {
            return x<0.5?0.5f * (1.0f - Mathf.Sqrt(1.0f - 4.0f * x * x)): 0.5f * (Mathf.Sqrt(1.0f - (x*2-2) * (x*2-2)) + 1 ); }
        },
         { CurveType.Back, (x)=>
        {
            return x * x * (2.70158f * x - 1.70158f); }
        },
        { CurveType.BackDouble, (x)=>
        {
            return x<0.5?x * x * (14.379636f * x - 5.189818f): ((x-1) * (x-1)  * (14.379636f * (x-1)  + 5.189818f) + 1.0f); }
        },
        { CurveType.Bounce, (x)=>
        {
            if (x < 0.363636f)
            {
                return  7.5625f * x * x;
            }
            else if (x < 0.72727f)
            {
                x -= 0.545454f;
                return (7.5625f * x * x + 0.75f);
            }
            else if (x < 0.909091f)
            {
                x -= 0.818182f;
                return (7.5625f * x * x + 0.9375f);
            }
            else
            {
                x -= 0.954545f;
                return (7.5625f * x * x + 0.984375f);
            }
        }
        },
        { CurveType.BounceDouble, (x)=>
        {
            if (x < 0.5f)
            {
                if (x > 0.318182f)
                {
                    x = 1.0f - x * 2.0f;
                    return  (0.5f - 3.78125f * x * x);
                }
                else if (x > 0.136365f)
                {
                    x = 0.454546f - x * 2.0f;
                    return  (0.125f - 3.78125f * x * x);
                }
                else if (x > 0.045455f)
                {
                    x = 0.181818f - x * 2.0f;
                    return  (0.03125f - 3.78125f * x * x);
                }
                else
                {
                    x = 0.045455f - x * 2.0f;
                    return  (0.007813f - 3.78125f * x * x);
                }
            }

            if (x < 0.681818f)
            {
                x = x * 2.0f - 1.0f;
                return  (3.78125f * x * x + 0.5f);
            }
            else if (x < 0.863635f)
            {
                x = x * 2.0f - 1.545454f;
                return  (3.78125f * x * x + 0.875f);
            }
            else if (x < 0.954546f)
            {
                x = x * 2.0f - 1.818182f;
                return  (3.78125f * x * x + 0.96875f);
            }
            else
            {
                x = x * 2.0f - 1.954545f;
                return  (3.78125f * x * x + 0.992188f);
            }
        }
        },
    };

Now that the presets are all complete, we can implement the SampleCurve function! It takes a curve and a sample point x; it returns the corresponding y on the curve. All of the variables are within [0,1].

/// <summary>
/// Returns a float y (0,1) on the curve corresponding to the given x (0,1).
/// </summary>
/// <returns></returns>
public static float SampleCurve(SKCurve curve, float x)
{
    x = Mathf.Clamp01(x);
    CurveType type = curve.curveType;
    Func<float, float> func = curve.isPreset ? curveFuncs[type] : curve.func;

    switch (curve.curveDir)
    {
        case CurveDir.In:
            return func(x);

				//reverse the result for the out direction
        case CurveDir.Out:
            return 1 - func(x);

        default:
            return -1;
    }
}

Our curve sampler is ready to use! To sample a preset curve is extremely easy:

//Sample the quadratic curve at 0.3f
float y = SKCurveSampler.SampleCurve(SKCurve.QuadraticIn, 0.3f);

//Sample the bounce curve at 0.72f
float y = SKCurveSampler.SampleCurve(SKCurve.BounceIn, 0.72f);

In the next tutorial, we will explore more use of the curve sampler. See you there!

*These are super useful in game development, remember to implement them on your own!

5 views0 comments
Post: Blog2_Post
bottom of page