Cel Animation In Processing

Processing provides a range of tools for frame-by-frame animation, and an official example introduces the steps to creating one. We first review the logic behind the example then develop them into an object-oriented approach which can accommodate interactive animation, as would be needed in a video game.

Loading Files

Before we begin animating, we must plan for the organizational challenges posed by animation. Between the time an animation is drafted and completed, still frames may be added or removed, renamed, re-sequenced or saved to a different file format. Furthermore, in this period, coders and artists will need to discuss which translations, rotations and scalings will be handled in code and which in a graphics program. What we don’t need in the midst of this maelstrom is the burden of manually updating file names supplied to loadImage whenever a change is made. To that end, we abstract the loading routine to handle folders rather than files.

This function creates a File object from a given path. sketchPath fills in the location of our sketch so all we have to worry about is adding the data/ folder and folders therein. Once we ensure that we are in a folder (or directory), we retrieve a list of files it contains. We then loop through each file, convert it to a String and then check its file extension. Since the reference for PImage specifies that .png.jpg.gif and .tga file formats are readable, we check for those. If there’s a match, we load the image and add it to an ArrayList and convert it to an array. We could use also use a HashMap if we wanted to retrieve each PImage not by index but by a key. The same template can be modified to import svg or other file types with their respective loader function and data type.

Although a texture atlas (sprite sheet) is a common way to simplify file management and increase efficiency, it is not used here due to the aforementioned volatilities in rapid prototyping — a coordinate on an atlas may not always point to the same image— and due to bleeding at the edges of a texture specified by uv coordinates, which will be illustrated below.

Sequencing Frames

If we take four frames of animation — each 32 x 32 pixels native scale — within a folder batFlight and plug them into the following code

we get this result

Four Frames Of Animation.

The function noSmooth preserves the crenelations of each pixel, rather than anti-aliasing, which can slow performance. Putting these frames into motion leads to our next challenge: sequencing.

Upon observing the result, we see that after the bat’s down stroke its wings snap to full extension. Once we reach the length of the frames array, 4 in this case, the modulo operator % ensures that we return to the element at the beginning, since 4 / 4 leaves a remainder of 0. (The animated gifs below do not represent a Processing sketch, but are used for comparison.)

Bat Flight Animation (1–2–3–4 Pattern).
Bat Flight Animation (1–2–3–4–3–2 Pattern).

The difference between these two is that of 1–2–3–4 vs. 1–2–3–4–3–2. The former resets to 1 after 3 frames; the latter ping-pongs back and forth between two poles, returning to frame 1 after 5 frames. We could ask the artist to add two frames of the upstroke to the animation, but not all animations may be oscillations intended for infinite loop. For cases where we want to express this ping-pong pattern, we can modify our earlier code with the following

This follows the same formula that beginning coders learn to bounce a ball between two walls in space — position + speed * direction. However, our bouncing occurs not in 2D space but in 1D time, where our speed is always 1 step, our walls are the start (0) and the end (frames.length -1) of the array and we can only go forward (1) or backward (-1).

Four frames of animation are not much to work with, so earlier we slowed the global frame rate of the sketch to 12 Frames Per Second (FPS). What if another animation in this project has twice as many frames but is intended to last just as long as the flap of a bat’s wings? We could set a policy with the artist about how many frames to dedicate to an arbitrary unit of action and what the global frame rate of the game’s animations will be. However, even if we hold fast to frame consistency, setting global frame rate may land us in trouble if we use draw to also respond to user input and calculate physics updates. We’ll tackle how to code an interval at which an animation advances to the next frame when we create a class for animation.

Creating An Animation Class

In the code below, we create an interval to use against frameCount, allowing us to control the internal frame rate of the animation without changing global frame rate. Within the if-block specifying this interval, we place the two options above for how to sequence the frames. We also consider that some animations will repeat infinitely while others will repeat a finite number of times.

We use a custom shape rather than image. Rotating via code defeats the purpose of 2D pixel frame animation to some extent, but the necessities of the game may dictate we do so. The pivot point for our bat is located neither in the top-left corner nor in the center of the image, which are the two options imageMode provides us. We need to create a custom pivot point around which our animation may turn. Furthermore, should we need to shift the uvs of the animation or revise our class to work with sprite atlases, the custom shape gives us the flexibility to do so.

If we test this rotation in the code

we get the image below. The orange dot represents the center of the image while the green dot represents the pivot around which it rotates. The pivot is a normalized vector which is scaled up to the expected scale within the constructor. Attempting to rotate on all three axes simultaneously with Euler angles will result in gimbal lock; since we do not expect that need to arise, we do not guard against it.

A Rotated Animation With Pivot (Green) And Center (Orange).

If we want to smooth the image, we can remove the hint and skip acquiring the PGraphicsOpenGL object so as to set textureSampling. However, visual artifacts may result from using texture instead of image, since the right side of the image bleeds over to the left and likewise with the top and bottom. The artifacts may be reduced by placing extra padding around the source image.

Visual Artifacts When Using Texture.

Switching Animations With A State Machine

Unlike traditional animation, animation for interactive media requires us to switch between a number of animations based on user input or other systemic changes. For example, an avatar may be looping an idle animation until the move forward button is pressed, in which case it switches to the walk animation. Even more complex, pressing the jump button may lead the avatar to show the jumping animation until gravity overcomes the upward momentum, and so it is necessary to switch to the falling animation.

A control structure which will handle some of these basic situations is a finite state machine, for which the video below is a nice, if dry, introduction.

As described, three key components of a state machine are:

1. States.
2. Transitions between states.
3. Events, or rules, which govern when transitions happen.

Introductory implementations of a state machine will often use an enumeration and an if-else block or switch-case. Each state is expressed as an element in enumeration, enum AnimState { Walk, Idle, Jump }. Each transition is an assignment of a variable of the enumeration’s type, AnimState currentState = AnimState.Idle;. Events may be governed by boolean expressions in if-statements, such as if(keyPressed && keyCode == UP) currentState = AnimState.Jump;. The output of the state machine is another if-else block, if(currentState == AnimState.Jump) /* Perform jump behavior */.

We would like to avoid maintaining and updating multiple if-else blocks or switch-cases where possible, and want to separate code for each animation state into its own container. Therefore, we create a StateMachine and State. Because state machines could be used to govern not only animations, but also game states (Title, Victory, Game Over, Playing, Paused, etc.) and behavior states (Aggro’ed, On Patrol, Suspicious, Knocked Out), we generalize our state machine to handle other usages. We also want discourage the mixing together of a game state and an animation state. To do all this, we make StateMachine a generic class — as long as a class extends (is a child of) the class State, the machine can work with it. For now E is stand-in for that child class.

We want to track the current state as well as the previous in case we need to revert. If the constructor receives at least one state, it will set the first one to the current state by default. Many of the functions to follow are wrappers around HashMap functionality. Two major exceptions are draw and set, the latter of which sets the current state to one available in the HashMap, calls the exit behavior of the previous state (if it exists) and then the enter behavior of the current state.

The class is labeled abstract because we do not want to create any objects directly from the State constructor(as we would with concrete classes), rather we want State to be a parent shared by all its children. Any work done by its constructor will be called by children’s constructors using super. By preceding certain functions with abstract, we mandate that our children or grandchildren define them. In other words, we don’t know at the moment exactly what enter, draw, exit, or events do, but we want assurance they will be there when called upon. Since an event may lead a state to call for a transition out of itself, it depends on the StateMachine to which it belongs to make the transition, so the parameter is specified. Lastly, name makes it easier to retrieve the State, as it serves as a key to the HashMap inside the StateMachine class.

We then define an ancestor of State which clarifies functionality specific to animations. We add a member property isPlaying to Animation in the event that the conclusion of one animation leads to the exit of an animation state.

We can now return to the main sketch to try out our state machine with these animations

Bat Still Animation.
Bat Transition Animation.

The goal is to minimize the number of functions we call in draw and to front-load all configuration onto our constructors. We are still not using the events function we’ve set up, relying instead on a hack if-statement for illustration’s sake. On one hand, our state machine is overkill if we want to animate a tree’s rustling leaves or a bobbing flower. On the other hand, this is not complex enough to handle an enemy actor or a player-controlled avatar.

Transition Between Animations.

These concerns hopefully are alleviated in recognizing that we can now extend AnimationState to a new class, HeroWalk for example, where the rules which govern transitioning into and out of walking can be coded in the events function. Alternatively, we can create an Actor class, then an Avatar class. The Avatar class could relay user inputs from a key listener or mouse to the state machine it stores as a member property. This is part of a larger design strategy — where future specific or complex applications of a class are uncertain, class inheritance can be used to sketch out the broader strokes and simpler use cases. Later on, classes can be extended and parent functions overridden.

It takes time to acclimate to such an approach. If we are used to reading towers of linear code in a single tab, it can be frustrating to stumble onto function definition less than seven lines in length, but consisting of function calls to functions defined in another tab. Processing has a handy feature to help us with this horizontal, nonlinear reading.

Show Usage In The Processing IDE.

If we right-click on a variable or function we can select Show Usage... to view the places where it appears in the code. Clicking on an entry in the pop-up menu will take us to that usage.

Conclusion

Processing is better suited to procedurally generated animation than to handcrafted cel animation, especially in comparison to other software (Aseprite, Animate). Hopefully the tutorial above will augment our IDE enough to prove suitable. We should remember that technical details form only a part of the larger picture. Whenever we are working in interactive media, we must be mindful of the 12 principles of animation. A suggested videos in parting, then, is