Action: A C++11 interpolation framework
Before I begin, the repo for Action can be found here.
Since Flip, almost three years ago now, I’ve used interpolation in every game that I’ve created – starting with Starling, and then moving to iTween in Unity, and SKActions in SpriteKit. Interpolation is used everywhere in games, from UI animations, to AI actions, animations, and even world building. It truly could be looked at as a universal tool in a game developer’s toolbox.
What is interpolation?
Interpolation is a method of constructing new data within the range of a discrete set of known data. Put simply, it’s the translating of one piece of data into another piece of data.
Casey Muratori, who worked on The Witness, has a great video on the basics of interpolation, which I highly, highly suggest watching. He explains bezier curves in 40 seconds better than other’s have done in half an hour.
I’ve been wanting to roll my own solution for a while now. Not necessarily because I think I can do it better, or include more features, but so I can say I’ve created my own, gain experience in creating the backend of such a system, and have another tool at my disposal.
Thus, Action was created.
What it is.
It is a time-based, self-contained interpolation framework. It contains the common list of easing functions, and the ideology of a
Single contains a value to interpolate, start/end values of that interpolation, a duration of how long the interpolation will take, a start-delay, a loop type (currently
None, Normal, or Yoyo), an easing type, and an optional callback to be called upon completion of the interpolation.
There are also
Sequence actions: the former is a list of
Singles that are run in concurrency, with an optional callback when the entire group is finished, and the latter is similar, except the
Singles run sequentially.
The value to be interpolated is a reference to memory, and thus can be applied to any data of any object: volume of a sound clip, scale of a transform, rotation of a quaternion, etc.
What it isn’t.
The easing functions (called
TimingFunctions in the code base) are exposed, so more can be added, but currently there is no support for bezier/custom curves. Because they’re stored as function pointers, there’s no way to include more parameters in the call without casting another
typedef. I’m experimenting to find the best way to implement this.
There currently is also no option to pause actions, but that is due to the fact of me focusing on the interpolation framework more than the timer it runs on.
How to use it.
Let’s create a value:
There are common interpolations generally referred to as
FromTo. The above is an example of
From could be modeled by doing:
Now let’s take that value to interpolate, and create a
Single from it:
There’s a lot of customization in
Note: For any variable that is not set in the method call, the default is constructed: 0 seconds for delay, 1 second for duration, no looping, no callback, and
Linear for the timing function.
Rather than calling one of the predefined methods, you can create a
Single on your own:
Action::addSingle automatically starts the interpolation (including its delay).
Groups and Sequences
Now what about groups and sequences?
Let’s first create a list of
You’ll notice here that we call
Action::createSingle as opposed to
Action::addSingle. The former simply returns a
Single object without adding it to the pool of actions, whereas the latter creates the
Single, adds it to the pool, but doesn’t return anything.
You can also create
Singles like above on your own and create a list of them that way.
After the list of singles is constructed, creating a
Sequence is very straightforward:
addSequenceFrom automatically starts the interpolations.
All that’s left is to call
Action::update(); in your main game loop (otherwise none of the actions will run).
A word on callbacks.
Callbacks are typedefs of
std::function<void()>. Thus, we can use
std::bind and lambda function notation to create callbacks:
The beauty of them being lambdas means any code can be executed as a callback. Going back to our example
t class from the start of this post:
Single with a callback is as simple as:
Or, using the above callback: