Flip-book animation

A flipbook animation consists of a series of still images that are shown in succession. The technique is very similar to traditional cell animation (see http://en.wikipedia.org/wiki/Traditional_animation). The technique offers limitless opportunities since each frame can be manipulated individually. However, since each frame is stored in a unique image, the memory footprint can be high. The smoothness of animation is also dependent on the number of images shown each second but increasing the number of images usually also increase the amount of work. Defold flipbook animations are either stored as individual images added to an Atlas, or as a Tile Source with all frames laid out in a horizontal sequence.

Animation sheet Run loop

Playing flip-book animations

Sprites and GUI box nodes can play flip-book animations and you have great control over them at runtime.

To run an animation during runtime you use the sprite.play_flipbook() function. See below for an example.
GUI box nodes
To run an animation during runtime you use the gui.play_flipbook() function. See below for an example.

The playback mode once ping-pong will play the animation until the last frame and then reverse the order and play back until the second frame of the animation, not back to the first frame. This is done so that chaining of animations becomes easier.

Sprite example

Suppose that your game has a “dodge” feature that allows the player to press a specific button to dodge. You have created four animations to support the feature with visual feedback:

A looping animation of the player character idling.
A looping animation of the player character idling while being in the dodging stance.
A play-once transition animation taking the player character from standing to dodging.
A play-once transition animation taking the player character from dodging back to standing.

The following script provides the logic:

local function play_idle_animation(self)
    if self.dodge then
        sprite.play_flipbook("#sprite", hash("dodge_idle"))
        sprite.play_flipbook("#sprite", hash("idle"))

function on_input(self, action_id, action)
    -- "dodge" is our input action
    if action_id == hash("dodge") then
        if action.pressed then
            sprite.play_flipbook("#sprite", hash("start_dodge"), play_idle_animation)
            -- remember that we are dodging
            self.dodge = true
        elseif action.released then
            sprite.play_flipbook("#sprite", hash("stop_dodge"), play_idle_animation)
            -- we are not dodging anymore
            self.dodge = false

GUI box node example

When selecting an animation or image for a node, you are in fact assigning the image source (atlas or tile source) and default animation in one go. The image source is statically set in the node, but the current animation to play can be changed in runtime. Still images are treated as one frame animations so changing an image means in run time is equivalent to playing a different flipbook animation for the node:

function init(self)
    local character_node = gui.get_node("character")
    -- This requires that the node has a default animation in the same atlas or tile source as
    -- the new animation/image we're playing.
    gui.play_flipbook(character_node, "jump_left")

Completion callbacks

The sprite.play_flipbook() and gui.play_flipbook() functions support an optional Lua callback function as the last argument. This function will be called when the animation has played to the end. The function is never called for looping animations. The callback can be used to trigger events on animation completion or to chain multiple animations together. Examples:

local function flipbook_done(self)
    msg.post("#", "jump_completed")

function init(self)
    sprite.play_flipbook("#character", "jump_left", flipbook_done)
local function flipbook_done(self)
    msg.post("#", "jump_completed")

function init(self)
    gui.play_flipbook(gui.get_node("character"), "jump_left", flipbook_done)

Did you spot an error or do you have a suggestion? Please let us know on GitHub!