Löve – What is Love? An Introduction
Since my old tutorials were made in 2010 it was time for an update. This is an updated tutorial on Löve. It assumes the Löve version 0.9.2 but a lot of this article might remain relevant to later versions.
In the age of Gamemaker and Unity Löve still stands out as one of my favorite rapid prototyping frameworks. Lua is ideal for the quick ‘n dirty approach where other languages like C# would be overkill. It makes sense to learn Lua and the Löve framework because Lua is used in many different applications today and also in other game engines likes pico-8. Another great feature of Löve is that it is also very easy to distribute, which makes your creations accessible to wider audience.
Multi-platform & Open
The idea behind love is that you create .love files which can be run by the love executable on every platform, e.g. linux, OSX or windows. The .love files are renamed .zip files in which you put your project source files. Another side effect of this is that you can always unzip any .love file you find, view the source and learn from it.
Documentation
The best way to start learning is to check out the wiki on love2d.org. You can find a Getting Started Guide there which helps you install and set up a basic working environment.
Löve is mainly build on the following component:
- SDL, simple directmedia layer
- OpenGL, well known graphics library
- OpenAL, for sounds
- LuaJIT a variant of Lua, as the programming language
The great thing is that you don’t have to know anything about the first three components to create a game with it! Really the only thing you need to learn is Lua.
For beginners, I highly recommend reading Programming in Lua by Roberto Ierusalimschy who also is the leading architect of the language. That said, I will be covering some of the basics of Lua in this tutorial series.
What can be created?
The frontpage of Löve has some excellent examples of games made with Löve. This includes games like Mari0 and Move Or Die.
You see, Löve is not only a fast and easy to learn language but also has the power to be used in bigger projects. I hope this series of tutorial can give you some help so you can start to build your dream games soon.
Performance: Tilemaps in Löve with spritebatch
A usual pattern in 2D games is the use of tilemaps. In Löve you can implement those in a variety of ways. In this article I want to compare tilemap implementations in Löve 0.9.1 using spritebatches.
I will use this to create an ascii display.
Timing
I will use the following code to measure the performance. It is an extended example from kikitos time function. It uses os.clock to measure the time before the execution and again after the execution.
time.lua:
local last
return function(title, f, n)
if title == "clear" then last = nil; return end
collectgarbage()
local n = n or 100000
local startTime = os.clock()
for i = 1, n do
f()
end
local endTime = os.clock()
local delta = endTime - startTime
if not last then last = delta end
print(title, delta, (delta) - last)
last = delta
end
Spritebatch
We will generate a spritebatch with a tilesheet image(aka texture atlas) and use the spritebatches add/set method to change the content. The spritebatch will have NxM size. I used Alloy_curses_12x12 from the dwarf fortress tileset repository.
local img = love.graphics.newImage("Alloy_curses_12x12.png")
local spritebatch = love.graphics.newSpriteBatch(img, 87*25, "stream")
local quads = {}
for x = 0, 255 do
local i,j = 1+(x%16), math.floor(x/16)
quads[x] = love.graphics.newQuad(i*12, j*12, 12, 12, img:getWidth(), img:getHeight())
end
We can also use the bind and unbind function on the spritebatch to bind the spritebatch into memory before we loop through the whole display grid.
Luajit Tables
We need two arrays: one to store the id and one to store the ascii value of the cell. Map is our display grid and idmap will store the id returned by adding the cells.
local map = {}
local idmap = {}
spritebatch:bind()
for i=0,86 do
map[i] = {}
idmap[i] = {}
for j=0,24 do
map[i][j] = 68
spritebatch:setColor(255,255,255)
idmap[i][j] = spritebatch:add(quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
local random = math.random
time("spritebatch, lua table", function()
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = random(255)
spritebatch:setColor(0,255,255)
spritebatch:set(idmap[i][j], quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
love.graphics.setColor(255,255,255,255)
love.graphics.draw(spritebatch)
end, 1000)
FFI Arrays
We use the ffi.new functions to generate a 2 Dimensional uint8_t array for the character display grid and uint16_t for the id grid.
local ffi = require "ffi"
local map = ffi.new("uint8_t[87][25]")
local idmap = ffi.new("uint16_t[87][25]")
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = 68
spritebatch:setColor(255,255,255)
idmap[i][j] = spritebatch:add(quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
local time = require "time"
time("spritebatch, ffi array", function()
local random = math.random
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = random(255)
spritebatch:setColor(0,255,255)
spritebatch:set(idmap[i][j], quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
love.graphics.setColor(255,255,255,255)
love.graphics.draw(spritebatch)
end, 1000)
Performance
Using Luajit tables is a lot faster than using FFI arrays:
spritebatch, ffi array 2.13035 0 spritebatch, lua table 0.577013 -1.553337
FFI Arrays are a lot slower, so you should stick to tables if you want to draw tiles fast.
Slime explains: “The FFI array is probably slower because the loop it’s used in can’t be compiled by the JIT (since C API functions – all of LÖVE’s API, for example – prevent JIT compilation for the block of code they’re called in.)”
One more thing for spritebatch
It’s probably a good idea to only use spritebatch:set when something has changed. You can do this by having two map tables. Check if the map table is different than the cache table and only then set the spritebatch tile.
spritebatch:bind()
for i=0,86 do
for j=0,24 do
if map[i][j] ~= old[i][j] then
old[i][j] = map[i][j]
spritebatch:setColor(255,255,255)
spritebatch:set(smap[i][j], quads[map[i][j]], i*12, j*12)
end
end
end
spritebatch:unbind()
I created an example lua file that can be used as a very simple base to render roguelike ascii games:gist. But of course this can be used for any other tile based game too.