Building an Interpolation Library in C#: Part 2 – Simple Easing

The last part of the series left us with a great Interpolator class to build on. In Part 2, we’re going to we’re going to add zazz to the tween by introducing easing function support.

Easing functions make animations look pretty and in game development, you’re going to want them. First we’re going to explore a basic linear easing function. Then we’re going to make a couple minor changes to our Interpolator class, utilizing delegates to add support for easing functions in a very extensible fashion. Last, we’re going to add a fancier easing function to stretch our legs a big.

Let’s take a quick peek at the goal before diving in:

The Linear Easing Function

Linear ‘easing’ is so basic that we’ve already implemented it in the last part. Our value property previously used a linear ‘easing’ function. It really doesn’t do any easing, but nevertheless, we’re going to create an easing function class to maintain a consistent paradigm.

The Linear class is so simple that we’re going to just write it all at once. We start with a static class then add one static function to it, None. The function is aptly named None because it really doesn’t do any easing at all. The logic within the function is almost a direct copy and paste from the value property of our Interpolator class in Part 1.

namespace Com.Naut.Math.Interpolation.Easing
{

	/// <summary>
	/// A static interpolator easing class that contains only linear interpolation functions.
	/// </summary>
	public static class Linear
	{

		/// <summary>
		/// An easing function used to interpolate values over time.
		/// </summary>
		/// <param name="time">The current time in the interpolation.</param>
		/// <param name="start">The starting value we're interpolating from.</param>
		/// <param name="delta">The total change of the interpolation from t=0 to t=d.</param>
		/// <param name="duration">The total duration of the interpolation.</param>
		/// <returns>The value on the easing curve at time t.</returns>
		public static float None(float time, float start, float delta, float duration)
		{
			return delta * time / duration + start;
		}

	}

}

Let’s put this thing to use.

Adding Easing Function Support to Interpolator

To start, let’s add a delegate to Interpolator file, just inside the namespace, but before the class. If you’ve never worked with delegates before… think of them like this- A delegate specifies what a function signature should look like. It’s like an interface, but for a function instead of a class. It tells our class ‘how’ to call a function even if it isn’t exactly sure what function it’s calling at compile time.

Our delegate, EasingFunction, declares a function signature like the one used in Linear.None.

	/// <summary>
	/// An easing function used to interpolate values over time.
	/// </summary>
	/// <param name="t">The current time in the interpolation.</param>
	/// <param name="b">The starting value we're interpolating from.</param>
	/// <param name="c">The total change of the interpolation from t=0 to t=d.</param>
	/// <param name="d">The total duration of the interpolation.</param>
	/// <returns>The value on the easing curve at time t.</returns>
	public delegate float EasingFunction(float t, float b, float c, float d);

With the EasingFunction delegate declared, we can now use it as a type in our Interpolator class. We start by adding an EasingFunction member, ease. By default, ease  Then we update the value property to call the easing function rather than stick to the hardcoded linear interpolation.

		/// <summary>The easing function used to compute values over time.</summary>
		public EasingFunction ease
		{
			get { return _ease; }
			set { _ease = value; }
		}
		private EasingFunction _ease;

		/// <summary>The current value of the tween between start and end.</summary>
		public float value
		{
			get { return _ease(_time, _start, _end - _start, _duration); }
		}

We’re almost there. The last thing we need to do is add support to our constructor. Let’s add an ease parameter to our constructor, then add an overloaded constructor that enables Linear.None easing if no other easing function is specified.

		/// <summary>
		/// Creates a new interpolator that tweens from start to end over the specified duration and eases 
		/// using the specified easing function
		/// </summary>
		/// <param name="start">The starting value to tween from.</param>
		/// <param name="end">The ending value to tween to.</param>
		/// <param name="duration">The amount of time to tween over in seconds.</param>
		/// <param name="ease">The easing function to interpolate with.</param>
		public Interpolator(float start, float end, float duration, EasingFunction ease)
		{
			this.duration = duration;
			_start = start;
			_end = end;
			_ease = ease;
		}

		/// <summary>
		/// Creates a new interpolator that tweens from start to end over the specified duration.
		/// By default, a simple linear interpolation is used.
		/// </summary>
		/// <param name="start">The starting value to tween from.</param>
		/// <param name="end">The ending value to tween to.</param>
		/// <param name="duration">The amount of time to tween over in seconds.</param>
		public Interpolator(float start, float end, float duration) : this(start, end, duration, Linear.None) { }

If you were to compile and run the code now, our Linear.None easing function would be put into play. That’s exciting, but unfortunately it does the exact same thing as Part 1. Let’s fix that.

Quadratic Easing

I don’t want this series to focus on easing, so I am going to gloss over the easing function internals and simply call it magic. All you need to know is that the class we’re about to write is a set of easing functions based on quadratic curves, and it comes in three flavors:

  • EaseIn – Start slow, end fast
  • EaseOut – Start fast, end slow
  • EaseInOut – Start slow, get fast, end slow

The Quadratic class is also simple and follows the same static-class / static-function paradigm as Linear. It is also worth noting that the easing functions match the EasingFunction delegate signature declaration.

namespace Com.Naut.Math.Interpolation.Easing
{

	/// <summary>
	/// A static interpolator easing class that contains quadratic curve interpolation functions.
	/// </summary>
	public static class Quadratic
	{

		public static float EaseIn(float t, float b, float c, float d)
		{
			return c * (t /= d) * t + b;
		}

		public static float EaseOut(float t, float b, float c, float d)
		{
			return -c * (t /= d) * (t - 2) + b;
		}

		public static float EaseInOut(float t, float b, float c, float d)
		{
			if ((t /= d / 2) < 1)
			{
				return c / 2 * t * t + b;
			}
			return -c / 2 * ((--t) * (t - 2) - 1) + b;
		}

	}

}

Now in your test file, you should be able to add Quad.EaseInOut (or another easing function) to your Interpolator constructor. Easing really is magic (or just math?). For those interested in easing functions, there is a ton of information available on the Googles.

Source Code

C# Interpolation Library – Part 2

I updated the unity demo to show off the new easing code. Have fun and play with it a little! If you’re feeling courageous, try adding your own easing function!

Continue to: Part 3 – Simple Looping

-S

 

Leave a Reply

Your email address will not be published. Required fields are marked *