Animations and Transient Callbacks

Animations in games are too hard. Usually they consist of creating some convoluted state machine, where the state is what they’re supposed to be doing, and the data is for how long they’ve been doing it. Now this is a fine model, but most of the time it isn’t formalized, and thus becomes a large switch or cascade of ifs. Hard to read, hard to maintain. Something needs to be done here.

Glop solves the problem using a queue of transient callbacks, basically a callback that knows when it’s not supposed to be called anymore. There’s a level of indirection around the queue, so some object can take it, store it away, and replace it with its own queue. You’ll see why this is important in a minute.

For example, let’s say you’re writing tetris. You’re dealing with a piece that, when “k” is pressed, you want to rotate clockwise. But you want it to rotate smoothly and not “jump” like many tetris implementations do. In the traditional “obscure state machine” model, you put it in a rotating state and record when it started rotating. You rotate it by the difference in seconds between the current time and the time it started, and when the difference exceeds π/2 you put it back into the rest state.

That seems to work. But what happens if the user presses the rotate button again while it’s rotating? Then you’ve got a problem. You can either ignore it, or set the initial time to “now” so that it jumps back and starts over. But there’s not a good way to make it rotate again after it’s done, which is the behavior I’d want as a game designer (an unskilled one, but I figure at least some of my ideas are sane).

With the transient callback queue it’s easy. You enqueue a “timeslice” functor whenever the button is pressed. The timeslice functor keeps calling the closure given to it (with the amount of time passed since its starting) every frame, and then quits when it’s done. Sounds familiar, but the interesting part is that you can enqueue it. Once it’s done, the next one starts and rotates it again. It looks something like this in code:

    method rotate_cw => sub {
        my ($self) = @_;
        enqueue $self Glop::Transient::timeslice(
                               pi/2, sub { $theta += $STEP });

That’s it! No complicated state machines; just a simple closure and a length of time. $_[0] inside the closure would refer to the amount of time passed since the timeslice reached the front of the queue, but we turned out not to need it here.

Now, what happens if you say “if they push down while it’s rotating, I want it to smoothly move down by one immediately.” To boot, you probably want the downs to queue independently of the rotates. It’s easy to do that, if a little more abstract.

    method BUILD => sub {
        my ($self) = @_;
        ($downq, $rotq) = $self->queue->fork;

That splits the queue into two different ones (you can split it into three just the same way, using the magic of Now you just use the two different queues and it all works nicely.

There are more kinds of transients than just the timeslice, as you may have guessed. There’s Transient::single, which just executes it once. There’s Transient::forever which, well, isn’t really transient. There’s Transient::indef (for indefinite; I may need to come up with a better name[1]), which goes until you say last TRANSIENT (which can be nicely shortened to just last in the absence of an inner loop). There may be others, and you can certainly add your own.

And that’s Glop’s take on animation. Now I have all the necessary information to start implementing the Actor base class.

[1] There needs to be a standardization of interface at some point. I figure that can wait, however, as I’m not sure all the spiffy interfaces I’m going to do. Nobody’s using Glop yet, and if they do, it’s their funeral since it’s very early in development. By OSCON it should be in a fairly interface-stable state. After all, I have the Glop::Draw->Cirlce style; the Glop::Transient::timeslice style; the just plain drawing {...} style. There needs to be some rhyme and reason to why I use each of these, and I may toss the second one altogether, so it becomes Glop::Transient->Timeslice.


1 thought on “Animations and Transient Callbacks

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s