There are 3 tile map scrolling tutorials. One even goes as far as to call itself "efficient", because it uses a sprite batch. Can you see what's common in them?
They go and move all of the sprites that a tile map is made from.
How is that anything even close to being efficient? That's the way you would draw on the CPU, with no hardware acceleration. It is now the year 2013, so let's get a little updated, ok?
Code: Select all
love.graphics.translate(x, y) -- A little more "efficient"
The Box2D (aka physics) callbacks tutorial doesn't show the last argument to postSolve. This argument is the actual impulse of the collision response, and it's quite important for anything that reacts to it (e.g. damage).
love.run: it is bad. More precisely, the dt parameter sent to love.update is bad.
If you are making any sort of movement over time that requires accuracy, you will see why.
The only way to have such movements not explode in your face badly (change over time), is to run the update loop at a constant rate, decoupling it from the drawing function.
The issue with a constant dt, is that moving graphics get "jittery", and don't move as smoothly. This is because you need interpolation between updates, since (most likely) the rendering function will run at a different speed.
If you can guarantee they both run at the same rate, all the good for you, however I don't suggest forcing this. Instead, the drawing routine simply gets an alpha value in the range of [0, 1] and you interpolate between movements using it when drawing.
Here is a love.run variant that does all this.
Note that dt in this variant defines the frames per second. 1 / 60 = 60 FPS for the update function (but not related to the drawing function).
maxframetime defines the biggest difference of seconds between frames.
If you don't put a maximum, and your game gets stuck for some time, your update function will suddenly be called many times. An example for this happens when in Windows you grab the title part of the Love2D window, wait any amount of time you want to, and then release it.
Code: Select all
function love.run()
math.randomseed(os.time())
math.random() math.random()
if love.load then love.load(arg) end
local dt = 1 / 60
local maxframetime = 0.25
local time = 0
local currenttime = love.timer.getTime()
local accumulator = 0
while true do
if love.event then
love.event.pump()
for e,a,b,c,d in love.event.poll() do
if e == "quit" then
if not love.quit or not love.quit() then
if love.audio then
love.audio.stop()
end
return
end
end
love.handlers[e](a,b,c,d)
end
end
local newtime = love.timer.getTime()
local frametime = newtime - currenttime
if frametime > maxframetime then
frametime = maxframetime
end
currenttime = newtime
accumulator = accumulator + frametime
while accumulator >= dt do
if love.update then love.update(dt) end
time = time + dt
accumulator = accumulator - dt
end
local alpha = accumulator / dt
if love.graphics then
love.graphics.clear()
if love.draw then love.draw(alpha) end
end
love.timer.sleep(0.001)
if love.graphics then love.graphics.present() end
end
end
Sprite batches, or "Why must you use them".
Drawing works on your graphics card. Your graphics card likes to get a low amount of big batches of data, instead of a big amount of small batches of data.
Sprite batches take all your sprites, and draw them in one go, conforming with what your graphics card wants.
If you ever tried to render a couple of thousands or more of sprites one by one, and your code became slow, this is why.
Now, using sprite batches is sometimes less obvious than using sprites alone, so here are a few suggestions how to make them easier to use.
You can only have one texture per sprite batch. Since you most likely need more than one, you will need to use a texture atlas.
A texture atlas is a big image, that has all your smaller images in it.
You can create a texture atlas in a program designed for it, or just about any drawing program.
However, there is another way, which people seem to shy from for some reason, and that is dynamically creating it in your code.
That is, you accept a list of images, and you create one big image that holds all of them.
The idea is to generate a blank (that is, black) image that you know is big enough to hold all your images. For example, a 512x512 image can hold 256 32x32 images.
You then fill it with your textures (ImageData:paste), and at the same time keep a map, that maps from image names / indices / anything you like, to the quads that describe them in your big image.
Here is a little sample, assuming no class() (plain old Lua):
Code: Select all
TextureAtlas = {}
-- "size" is the size of the texture atlas, "textureSize" is the size of each image and "textures" is an array of file names
function TextureAtlas:new(size, textureSize, textures)
local self = {imageData = love.image.newImageData(size.x, size.y), image = {}, textureMap = {}}
local w = math.floor(size.x / textureSize.x)
local h = math.floor(size.y / textureSize.y)
for i, v in ipairs(textures) do
local imageData = love.image.newImageData(v)
local offset = (i - 1) * textureSize.x
local x = math.floor(offset % size.x)
local y = math.floor(offset / size.x) * textureSize.y
-- Paste the image to the texture atlas
self.imageData:paste(imageData, x, y, 0, 0, textureSize.x, textureSize.y)
-- And add a quad to the mapping table
self.textureMap[v] = love.graphics.newQuad(x / textureSize.x, y / textureSize.y, 1, 1, w, h)
end
self.image = love.graphics.newImage(self.imageData)
self.image:setFilter("nearest", "nearest")
setmetatable(self, { __index = TextureAtlas})
return self
end
function TextureAtlas:getImage()
return self.image
end
-- Get the quad that maps to textureName
function TextureAtlas:map(textureName)
return self.textureMap[textureName]
end
Mapping coordinates to sprites in sprite batches.
At first, it might seem hard to add, edit or remove specific sprites from a sprite batch (and there were many questions about this), but it is actually very easy.
The idea is, like the texture atlas example above, to map the sprite indices inside the batches to something you can easily use.
As an example, if you have a tile map, it makes sense to map sprites in the batch to [X, Y] coordinates, so let's do that.
Code: Select all
tileMap = {}
for y = 1, height do
for x = 1, width do
-- Let's get the texture quad we want from the texture atlas
local quad = textureAtlas:map(...)
-- Our id for this sprite inside the batch
local id = batch.addq(quad, x, y)
-- Map this batch ID to its [x, y] coordinate, and also add the tile type for later processing
tileMap[x .. "x" .. y] = {id = id, tiletype = ...}
end
end
As a continuation, you might have noticed I used the [x, y] numbers directly in the sprite position This is the next suggestion.
If you are working on a tile map, always work in tile-units.
That is, if you are talking about "position", "size", "distance", or anything related to them your world, then "1" is "tile", not pixel. Pixels don't matter.
If you express all the sizes in your game in tile-space, you will not require many, many calculations to convert between pixels and tiles, which otherwise you will have to do in initialization, logic, rendering, physics, and what not.
Once everything is expressed in tiles, and you want to actually draw it, it's a simple love.graphics.scale(pixels) to draw everything in the size you want in pixels.
I'll see if I got anything else (I most likely do, but I can't think of anything at the moment).