How to Implement Easing in Your Games

 

First, I'd like to give credit to Robert Penner for his well-known and widely adopted easing equations. Although they were initially developed for Flash, and later ported to Javascript and implemented in the Jquery framework, they have been ported to many languages and frameworks. In this article, I will share my implementation of Penner's work, which I adapted for use in the c++ SFML library. As always, if you'd like to skip the explanations, then stop by my repo on github.

Essentially, the approach I took was to simply throw each Penner easing function into one class, with each one returning a float. Pretty simple interface - the implementation is straight-forward, but first here is the definition of the easing class:


#ifndef INTERPOLATE_HPP
#define INTERPOLATE_HPP

/////////////////////////////////
//c++ port of Penner easing
//equations specifically with
//SFML in mind
//
//Adapted from c++ port by Jesus Gollonet
//http://www.jesusgollonet.com/blog/2007/09/24/penner-easing-cpp
//t: start time
//b: starting value being interpolated
//c: change in value
//d: duration
/////////////////////////////////

#include <math.h>
#include <algorithm>

#define PI 3.14

class interpolate
{
    public:
        interpolate();
        ~interpolate();

        static float linear(float t, float b, float c, float d);
        static float expoEaseIn(float t, float b, float c, float d);
        static float expoEaseOut(float t, float b, float c, float d);
        static float expoEaseInOut(float t, float b, float c, float d);
        static float cubicEaseIn(float t, float b, float c, float d);
        static float cubicEaseOut(float t, float b, float c, float d);
        static float cubicEaseInOut(float t, float b, float c, float d);
        static float quarticEaseIn(float t, float b, float c, float d);
        static float quarticEaseOut(float t, float b, float c, float d);
        static float quarticEaseInOut(float t, float b, float c, float d);
        static float quinticEaseIn(float t, float b, float c, float d);
        static float quinticEaseOut(float t, float b, float c, float d);
        static float quinticEaseInOut(float t, float b, float c, float d);
        static float quadraticEaseIn(float t, float b, float c, float d);
        static float quadraticEaseOut(float t, float b, float c, float d);
        static float quadraticEaseInOut(float t, float b, float c, float d);
        static float sineEaseIn(float t, float b, float c, float d);
        static float sineEaseOut(float t, float b, float c, float d);
        static float sineEaseInOut(float t, float b, float c, float d);
        static float circularEaseIn(float t, float b, float c, float d);
        static float circularEaseOut(float t, float b, float c, float d);
        static float circularEaseInOut(float t, float b, float c, float d);
        static float backEaseIn(float t, float b, float c, float d);
        static float backEaseOut(float t, float b, float c, float d);
        static float backEaseInOut(float t, float b, float c, float d);
        static float elasticEaseIn(float t, float b, float c, float d);
        static float elasticEaseOut(float t, float b, float c, float d);
        static float elasticEaseInOut(float t, float b, float c, float d);
};

#endif // INTERPOLATE_HPP

For each type of ease, there is a corresponding function. For a quick reference of what each function accomplishes check out this page. Although many of the examples you'll find on the web deal with interpolation of position, you can use these functions for manipulating many other properties as well. For example, in SFML you may want to move the view/viewport smoothly as part of a cutscene or perhaps you would like to animate the color or opacity of an object in your scene smoothly - all of these things and many more can be done using this class. Here is the full implementation:


#include <interpolate.hpp>

interpolate::interpolate()
{

}

interpolate::~interpolate()
{

}

float interpolate::linear(float t, float b, float c, float d)
{
    return c * (t/d) + b;
}

float interpolate::expoEaseIn(float t, float b, float c, float d)
{
    return (t==0) ? b : c * pow(2, 10 * (t/d - 1)) + b;
}

float interpolate::expoEaseOut(float t, float b, float c, float d)
{
    return (t==d) ? b+c : c * pow(2, 1-(10 * t/d)) + b;
}

float interpolate::expoEaseInOut(float t, float b, float c, float d)
{
    if (t==0)
        return b;
    if (t==d)
        return b+=c;
    if ((t/=d/2) < 1)
        return c/2 * pow(2, 10 * (t - 1)) + b;

    return c/2 * pow(2, 1-(10 * --t)) + b;
}

float interpolate::cubicEaseIn(float t, float b, float c, float d)
{
    return (t==0) ? b : c * pow(3, 10 * (t/d - 1)) + b;
}

float interpolate::cubicEaseOut(float t, float b, float c, float d)
{
    return (t==d) ? b+c : c * pow(3, 1-(10 * t/d)) + b;
}

float interpolate::cubicEaseInOut(float t, float b, float c, float d)
{
    if (t==0)
        return b;
    if (t==d)
        return b+=c;
    if ((t/=d/2) < 1)
        return c/2 * pow(3, 10 * (t - 1)) + b;

    return c/2 * pow(3, 1-(10 * --t)) + b;
}

float interpolate::quarticEaseIn(float t, float b, float c, float d)
{
    return c*(t/=d)*t*t*t + b;
}

float interpolate::quarticEaseOut(float t, float b, float c, float d)
{
    return -c * (1-(t=t/d-1)*t*t*t - 1) + b;
}

float interpolate::quarticEaseInOut(float t, float b, float c, float d)
{
    t /= d/2;
    if (t < 1)
        return c/2*t*t*t*t + b;
    t -= 2;
        return 1-(-c/2 * (t*t*t*t - 2)) + b;
}

float interpolate::quinticEaseIn(float t, float b, float c, float d)
{
    return c*(t/=d)*t*t*t*t + b;
}

float interpolate::quinticEaseOut(float t, float b, float c, float d)
{
    return c*(t=t/d-1)*(1-(t*t*t*t + 1)) + b;
}

float interpolate::quinticEaseInOut(float t, float b, float c, float d)
{
    if ((t/=d/2) < 1)
        return c/2*t*t*t*t*t + b;

    return 1-(c/2*((t-=2)*(t*t*t*t) + 2)) + b;
}

float interpolate::quadraticEaseIn(float t, float b, float c, float d)
{
    return c*(t/=d)*t + b;
}

float interpolate::quadraticEaseOut(float t, float b, float c, float d)
{
    return 1-(-c *(t/=d)*(t-2)) + b;
}

float interpolate::quadraticEaseInOut(float t, float b, float c, float d)
{
    if ((t/=d/2) < 1)
        return ((c/2)*(t*t)) + b;

    return 1-(-c/2 * (((--t)*(t-2)) - 1)) + b;
}

float interpolate::sineEaseIn(float t, float b, float c, float d)
{
    return -c * cos(t/d * (PI/2)) + c + b;
}

float interpolate::sineEaseOut(float t, float b, float c, float d)
{
    return c/2 * cos(t/d * (PI/2)) + b;
}

float interpolate::sineEaseInOut(float t, float b, float c, float d)
{
    if (t < 0.5f)
        return c/=d;

    return 1-(-c/2 * (cos(PI*t/d) - 1)) + b;
}

float interpolate::circularEaseIn(float t, float b, float c, float d)
{
    return -c * (sqrt(1 - (t/=d)*t) - 1) + b;
}

float interpolate::circularEaseOut(float t, float b, float c, float d)
{
    return 1-(c * sqrt(1 - ((t=t/d-1)*t))) + b;
}

float interpolate::circularEaseInOut(float t, float b, float c, float d)
{
    if ((t/=d/2) < 1)
        return -c/2 * (sqrt(1 - t*t) - 1) + b;

    return 1-(c/2 * (sqrt(1 - t*(t-=2)) + 1)) + b;
}

float interpolate::backEaseIn(float t, float b, float c, float d)
{
    float s = 1.70158f;
    float postFix = t/=d;
    return c*(postFix)*t*((s+1)*t - s) + b;
}

float interpolate::backEaseOut(float t, float b, float c, float d)
{
    float s = 1.70158f;
    return 1-(c*((t=t/d-1)*t*((s+1)*t + s) + 1)) + b;
}

float interpolate::backEaseInOut(float t, float b, float c, float d)
{
    float s = 1.70158f;
    if ((t/=d/2) < 1)
        return c/2*(t*t*(((s*=(1.525f))+1)*t - s)) + b;

    float postFix = t-=2;
    return 1-(c/2*((postFix)*t*(((s*=(1.525f))+1)*t + s) + 2)) + b;
}

float interpolate::elasticEaseIn(float t, float b, float c, float d)
{
    if (t==0)
        return b;
    if ((t/=d)==1)
        return b+c;
    float p=d*.3f;
    float a=c;
    float s=p/4;
    float postFix =a*pow(2,10*(t-=1));
    return -(postFix * sin((t*d-s)*(2*PI)/p )) + b;
}

float interpolate::elasticEaseOut(float t, float b, float c, float d)
{
    if (t==0)
        return b;
    if ((t/=d)==1)
        return b+c;
    float p=d*.3f;
    float a=c;
    float s=p/4;
    return 1-(a*pow(2,-10*t) * sin( (t*d-s)*(2*PI)/p ) + c + b);
}

float interpolate::elasticEaseInOut(float t, float b, float c, float d)
{
    if (t==0)
        return b;
    if ((t/=d/2)==2)
        return b+c;
    float p=d*(.3f*1.5f);
    float a=c;
    float s=p/4;

    if (t < 1)
    {
        float postFix =a*pow(2,10*(t-=1));
        return -.5f*(postFix* sin( (t*d-s)*(2*PI)/p )) + b;
    }
    float postFix =  a*pow(2,-10*(t-=1));
    return 1-(postFix * sin( (t*d-s)*(2*PI)/p )*.5f + c + b);
}

All of these functions return a float which can be passed to any SFML object - text, sprite, view, etc. - that have properties you are interested in changing over time. As it is more difficult (and tedious) to explain in words what these functions do, I put together a showcase utilizing this class. There are also better explanations out there if you fire up your favorite search engine. ;)

The showcase animates the position of some shapes using these interpolation functions. Here is some footage of the demo:

Don't forget to stop by the repo on github and grab the source.