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.
Introducing Action.
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
action. 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 Group
and 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.
First, include Action.h
.
Let’s create a value:
There are common interpolations generally referred to as To
, From
, and FromTo
. The above is an example of FromTo
. To
and From
could be modeled by doing:
Singles
Now let’s take that value to interpolate, and create a Single
from it:
There’s a lot of customization in addSingle
:
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 Singles
:
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 Group
or Sequence
is very straightforward:
Like Singles
, Action::addGroupFrom
and 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:
Creating a Single
with a callback is as simple as:
Or, using the above callback: