draw entities based on their depth

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
RonanZero
Citizen
Posts: 90
Joined: Mon Oct 20, 2014 3:33 am

draw entities based on their depth

Post by RonanZero »

so right now my game just draws entities based on the order they were created (newest ones on top) which is normal since it just goes through the entities table and every entities place in the table is determined by the number of entities created since the game started plus one. but how would i make it so instead of looping through them normally, i could sort them first based on their depth?
while true do end;
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: draw entities based on their depth

Post by micha »

Lua provides a sorting function for tables and it even allows you to sort by depth. Have a look here for example: Table tutorial.
User avatar
Jasoco
Inner party member
Posts: 3725
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: draw entities based on their depth

Post by Jasoco »

I'll just post an answer I gave to someone via PM today. (Which ended up being the right answer to the wrong question) It's a drawPool library I created. Basically does exactly what was posted above. You put all your drawables in a numbered table as reference data, sort that table by a certain parameter (In my case "layer") then run through it drawing each drawable based on the data you put in the table. I'll let my post explain:


It's definitely near perfect. But it's not perfect. 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:setScissor(layer, vars)
-- 	self.pool[#self.pool+1] = {vars = vars, layer = layer or 0, kind = "scissor"}
-- 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.colorize then
			-- 	love.graphics.setShader(fogShader)
			-- 	fogShader.send(fogShader, "tint", { d.color[1] / 255, d.color[2] / 255, d.color[3] / 255, (d.color[4] or 255) / 255 })
			-- else
				lgr.setColor(d.color or {255,255,255})
			-- end
			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
			-- if d.colorize then
			-- 	love.graphics.setShader()
			-- 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
		-- elseif d.kind == "scissor" then
		-- 	-- print(d.vars)
		-- 	if d.vars then
		-- 		lgr.setScissor(d.vars[1], d.vars[2], d.vars[3], d.vars[4])
		-- 	else
		-- 		lgr.setScissor()
		-- 	end
		end
	end
end

return drawPool
Post Reply

Who is online

Users browsing this forum: No registered users and 203 guests