- 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!