Animation Tutorial
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