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.