2DEngine

Animation tutorial

Contents

Introduction

In this short tutorial, we are going to look at a simple and straightforward way to play animations. The animations will be loaded from a sprite sheet image, Sprite sheets are basically sequences of frames saved as a single image.

Animation frames

There are many techniques for producing animations. In order to keep our code reusable, we are going to build an animation object using closures. If you are unfamiliar with object-oriented programming (in Lua) it would be a good idea to first go over the official Lua documentation.

The resulting animation object takes in an image and the number of rows and columns. The number of rows and columns refers to the frames contained in the sprite sheet image. The rest of the parameters are optional.

We split the image into quads, where each quad represents one frame of the animation. This is very efficient, since we will only need to switch between quads when drawing.

return function(sheet, rows, cols, frames, delay, elapsed)
  -- optional parameters
  frames = frames or (rows*cols)
  delay = delay or (1/30)
  elapsed = elapsed or 0
  
  -- pre-calculate quads
  local sw, sh = sheet:getDimensions()
  local tw, th = sw/cols, sh/rows
  local quads = {}
  for y = 0, cols - 1 do
    for x = 0, rows - 1 do
      local q = love.graphics.newQuad(x*tw, y*th, tw, th, sw, sh)
      table.insert(quads, q)
      if #quads > frames then
        break
      end
    end
  end
  -- animation object
  local anim = {}
  ...
  return anim
end

Animation loops

It's easy to make an animation that loops forever. In practice, we often need to play reversed or "bouncing" animations that are repeated a specific number of times. Controlling the playback of animations involves a little bit of math.

Loop Bounce

The "loop" variable determines how many times the animation will be repeated. Setting the "loop" variable to 0 means that the animation will loop forever. The "reverse" flag starts and plays the animation in reverse. There is also the "bounce" flag, which automatically reverses the playback.

  -- internal values
  local frame = 0
  local playing = false
  local loop = 0
  local bounce = false
  local reverse = false
  
  -- animation object
  local anim = {}
  -- update
  function anim.sync(dt)
    if not playing then
      return
    end
    elapsed = elapsed + dt
    local i = math.floor(elapsed/delay)
    -- limit number of repeats
    local n = frames - 1
    if loop > 0 and i > loop*n then
      i = loop*n
      playing = false
    end
    -- calculate the current frame number
    if bounce then
      frame = n - math.abs(i%(n*2) - n)
    else
      frame = i%(n + 1)
    end
    if reverse then
      frame = n - frame
    end
  end
  ...

Playing animations

For this example, we are using an "explosion" animation saved in PNG format. The sprite sheet texture has a total of 16 frames aligned in 4 rows by 4 columns. Note that the frames must be arranged in a grid and are exactly the same size.

One way to put everything together is to save the code as a seperate file: "animation.lua". This will work as a standalone module and makes using our animation object super easy.

love.graphics.newAnimation = require("animation")

image = love.graphics.newImage("explosion.png")
animation = love.graphics.newAnimation(image, 4, 4)
-- loop ten times
animation:play(10)

function love.update(dt)
  animation.sync(dt) 
end
function love.draw()
  animation.draw(0, 0)
end

Download the source code