perfect. But it's not
. It's also very simple.
Basically I don't draw anything directly to the screen. I add each drawable's arguments to a numbered table and give it a type (Rectangle, polygon, image, circle, any other drawable) and a "distance from camera" value which ends up as the sort order.
I then sort this table by it's "distance" value.
And lastly, run through the table, check the type of object, and draw that object of that type using the arguments.
I kind of created a sort of library which I call "drawPool" which demonstrates what I mean. Here's the code to look at. Try putting it in a .lua and see if you can use it as is.
Code: Select all
--[[
2014-2015 Jason Anderson
This is a library I guess. Not really. It doesnt have a license because I dont know anything about licenses. I guess whatever license lets you use it as you wish. I dont claim to own any of this code as it's just code. I did write it myself, but it's nothing special. It also may not work as-is without other stuff or modification, but it probably will actually. Feel free to add your own "kinds" and delete code you want. Just I guess keep my name somewhere or whatever. I dunno.
How to use:
1) Require the library in your main.lua as such:
drawPool = require 'drawPool'
Or you can load it at any time and as many times as you need like this:
drawPool = love.filesystem.load("drawPool.lua")()
This allows you to have as many pools as you need to and layer them separately. For instance, background layers and foreground layers. Example:
drawPool = {}
for i = 1, 3 do
drawPool[i] = love.filesystem.load("drawPool.lua")()
end
As long as you run through the drawPool table for each :prepare() and :present() and you :push() the proper objects to the proper drawPool[#] then it'll work perfectly.
2) Call drawPool:prepare() at the beginning of your :update() function. No arguments are required.
3) Any time before presenting, use the drawPool:push() function to add objects to the pool. The arguments should be in the form of a table. Use the standard naming conventions like x or y or w or h (Not width or height, unless it's the text kind. (Note: I use "kind" instead of "type" to avoid conflicting with the type() function.) Sorry, it's inconsistent.) And use the layer argument to determine what the sort order is. Remember, pool objects with the same sort order may fight for priority due to how table.sort works. So I find it better to add a tiny random decimal amount to each object's layer to try and make sure they wont fight.
Examples:
drawPool:push { kind = "rect", x = 100, y = 100, w = 100, h = 100, color = {255,0,0}, layer = 100 }
drawPool:push { kind = "circle", x = 200, y = 120, rad = 80, color = {0,0,255}, layer = 95 }
drawPool:push { kind = "text", x = 50, y = 200, width = 300, align = "center", shadow = true, font = your_font, color = {255,255,255}, layer = 103 }
drawPool:push { kind = "image", x = 80, y = 60, image = fluffyBunny, quad = bunnyQuad[1], rot = 0, sx = 1, sy = 1, iox = 0, ioy = 0, layer = -304.43 }
Where sx/sy is Scale and iox/ioy is Image Offset. Quad is optional. Font is optional. Shadow is optional.
setScissor is not really required in anything. I added it as a hack for my UI system but dont really think I need it now anyway. It's too hacky and clunky. It can probably be removed.
Colorize requires a shader I made but isnt an important or needed part of the library.
4) In your :draw() function, call drawPool:present(), but only when you're ready to draw everything. Any :push() calls made after :present() will not happen.
What happens is every frame, a table is emptied and stuff is added to it, sorted, then iterated through in order to determine what to draw in what order. This lets you basically "draw" any object at any time without worrying whether it's going to be drawn in front of or behind another object. As long as it gets a proper "layer" it'll draw at the right time. You can set layers to really high amounts for certain things (Like UI) and lower amounts for others (Game stuff) for example.
Also note, this method can create a lot of garbage if used too much each frame. But it's usually not a problem.
You can use this method to completely replace Löve's drawing frontend if you need to and push every single UI element into a pool if you really wanted to. You can use it for the game graphics or for the UI if you need to. I've used it for flat-shaded and textured polygonal 3D games myself. It was originally concieved for a 2D side-scrolling beat-em-up (Like TMNT) to make sure characters drew in the correct order. Something a lot of NES games in the day didnt bother to do.
There is officially more text in this README than the entire library.
]]
local lgr = love.graphics
local v = ... or {}
local drawPool = {
name = v.name or "No Name"
}
function drawPool:prepare()
self.pool = {}
end
function drawPool:push(v)
if not v.layer then v.layer = 0 end
if not v.color then v.color = {255,255,255} end
if v.kind == "text" then
if not v.font then v.font = font.tiny end
elseif v.kind == "rect" or v.kind == "circle" then
if not v.fill then v.fill = "fill" end
if v.fill == "line" and not v.lineWidth then v.lineWidth = 1 end
elseif v.kind == "line" then
if not v.lineWidth then v.lineWidth = 1 end
end
self.pool[#self.pool+1] = v
end
function drawPool:present()
-- print("Name:", self.name)
table.sort(self.pool, function(a, b)
-- print(a.layer, b.layer)
return a.layer < b.layer
end)
for i = 1, #self.pool do
local d = self.pool[i]
if d.kind == "image" then
if d.quad then
lgr.draw(d.image, d.quad, d.x, d.y, d.rot or 0, d.sx or 1, d.sy or 1, d.iox or 0, d.ioy or 0)
else
lgr.draw(d.image, d.x, d.y, d.rot or 0, d.sx or 1, d.sy or 1, d.iox or 0, d.ioy or 0)
end
elseif d.kind == "rect" then
if d.lineWidth then lgr.setLineWidth(d.lineWidth) end
lgr.setColor(d.color)
lgr.rectangle(d.fill, d.x, d.y, d.w, d.h)
elseif d.kind == "line" then
lgr.setLineStyle("rough")
if d.lineWidth then lgr.setLineWidth(d.lineWidth) end
lgr.setColor(d.color)
lgr.line(d.points)
elseif d.kind == "circle" then
if d.lineWidth then lgr.setLineWidth(d.lineWidth) end
lgr.setColor(d.color)
lgr.circle(d.fill, d.x, d.y, d.rad, d.detail)
elseif d.kind == "text" then
local text = d.text or ""
lgr.setFont(d.font)
if d.width then
if d.shadow then
lgr.setColor(0,0,0)
lgr.printf(text, d.x+1, d.y+1, d.width, d.align)
end
lgr.setColor(d.color)
lgr.printf(text, d.x, d.y, d.width, d.align)
else
if d.shadow then
lgr.setColor(0,0,0)
lgr.print(text, d.x+1, d.y+1)
end
lgr.setColor(d.color)
lgr.print(text, d.x, d.y)
end
end
end
end
return drawPool