How to force draw an image on top of another image?

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
Kitsune_Dev
Prole
Posts: 2
Joined: Sat Mar 30, 2024 9:45 am

How to force draw an image on top of another image?

Post by Kitsune_Dev »

Hi, I'm very new to Love2D, just started the other night, I ran into a problem with rendering images

specifically with rendering images on top of another, currently my game seems buggy when there's a not of images on the screen because the render order changes so I was wondering if it is possible to force render an image on top of another image like a ZIndex or Render order?

Here's my .love file
Starter.love
(104.69 KiB) Downloaded 66 times
Here's a screenshot of it
Screenshot 2024-03-30 025325.png
Screenshot 2024-03-30 025325.png (724.53 KiB) Viewed 1990 times
What I want is the coins that are created first to be under the coins that are created after and not appear in a random order like this

Also here's my Source Code

Code: Select all

local count = 0
local output = ""

local IMAGES = {}

local ImagesToDraw = {}

local function registerImages()
	for i, filePath in ipairs(love.filesystem.getDirectoryItems("/Images")) do
		IMAGES[i] = "/Images/" .. filePath
	end
end
registerImages()

local SOUNDS = {}

local function registerSounds()
	for i, filePath in ipairs(love.filesystem.getDirectoryItems("/Sounds")) do
		SOUNDS[i] = "/Sounds/" .. filePath
	end
end
registerSounds()

local function getRandom()
	local index = love.math.random(1, #SOUNDS)
	return love.audio.newSource(SOUNDS[index], "static")
end

local function increment()
	count = count + 1
end

local function playCoinSFX()
	local sound = getRandom()

	sound:play()
end

local currentCoins = 0

local function drawImages()
	for image, imageData in pairs(ImagesToDraw) do
		imageData.y = imageData.y + 1
		imageData.r = imageData.r + 0.02

		if imageData.y < 600 then
			love.graphics.draw(image, imageData.x, imageData.y, imageData.r, 0.5, 0.5, 256/2, 256/2)
		else
			image:release()
			ImagesToDraw[image] = nil
			currentCoins = currentCoins - 1
		end
	end
end

function love.draw()
	love.graphics.print(count, 400, 300)

	love.graphics.print(output, 400, 400)

	drawImages()
end

local MAX_COINS = 100
local LAST_COIN = love.timer.getTime()

local function canCreateCoin()
	return love.timer.getTime() - LAST_COIN > 0.1 and currentCoins < MAX_COINS
end

local function createCoin()
	LAST_COIN = love.timer.getTime()
	currentCoins = currentCoins + 1
	local image = love.graphics.newImage(IMAGES[1])
	ImagesToDraw[image] = { ["image"] = image, x = love.math.random(0, 600), y = -100, r = 0 }

	output = LAST_COIN
end

local function playEffect()
	if canCreateCoin() then
		createCoin()
		increment()
		playCoinSFX()
	end
end

local function registerEvents()
	function love.keypressed()
		playEffect()
	end

	function love.mousepressed()
		playEffect()
	end

	function love.mousemoved()
		playEffect()
	end
end
registerEvents()
User avatar
slime
Solid Snayke
Posts: 3162
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: How to force draw an image on top of another image?

Post by slime »

Images appear back-to-front in the order they're drawn - however the pairs iterator function doesn't have a defined order, so you can't rely on it for things that should be ordered.

Using an array and iterating with ipairs is the usual way to do it instead.
Kitsune_Dev
Prole
Posts: 2
Joined: Sat Mar 30, 2024 9:45 am

Re: How to force draw an image on top of another image?

Post by Kitsune_Dev »

slime wrote: Sat Mar 30, 2024 12:13 pm Images appear back-to-front in the order they're drawn - however the pairs iterator function doesn't have a defined order, so you can't rely on it for things that should be ordered.

Using an array and iterating with ipairs is the usual way to do it instead.
thank you for the information, that's actually what I thought at first but I was hoping for an easier solution, is there a library available that does array manipulation?
User avatar
Bobble68
Party member
Posts: 162
Joined: Wed Nov 30, 2022 9:16 pm
Contact:

Re: How to force draw an image on top of another image?

Post by Bobble68 »

Kitsune_Dev wrote: Sat Mar 30, 2024 8:49 pm
is there a library available that does array manipulation?
You don't really need one - you can just use table.insert() to add it to an ordered table like this:

Code: Select all

local coinImage = love.graphics.newImage(IMAGES[1])

local function createCoin()
	LAST_COIN = love.timer.getTime()
	currentCoins = currentCoins + 1
	table.insert(ImagesToDraw, { image = coinImage, x = love.math.random(0, 600), y = -100, r = 0 })
  
	output = LAST_COIN
end

[...]

local function drawImages()
	for i, imageData in ipairs(ImagesToDraw) do
		imageData.y = imageData.y + 1
		imageData.r = imageData.r + 0.02
    
		local image = imageData.image

		if imageData.y < 600 then
			love.graphics.draw(image, imageData.x, imageData.y, imageData.r, 0.5, 0.5, 256/2, 256/2)
		else
			image:release()
			ImagesToDraw[image] = nil
			currentCoins = currentCoins - 1
		end
	end
end
These are some other unrelated pointers, currently your code loads an image every time a coin is created. This isn't recommended since you can use the same image for all of the coins and newImage is a fairly slow function that will increase the memory of your project, so here I made it that there is a shared coinImage object that all coins will use, though I wouldn't recommend using this setup for larger projects.

I noticed you have a function where you get the addresses of all images in your project - what you could do here instead is load them to a table which you can then access later, something like this:

Code: Select all

local function registerImages()
	for i, filePath in ipairs(love.filesystem.getDirectoryItems("/Images")) do
		IMAGES[i] = love.graphics.newImage("/Images/" .. filePath)
	end
end
This way, they get loaded when the project starts, and can be indexed later
Dragon
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to force draw an image on top of another image?

Post by pgimeno »

Bobble68 wrote: Sun Mar 31, 2024 11:18 am

Code: Select all

[...]

local function drawImages()
	for i, imageData in ipairs(ImagesToDraw) do
		[...]
		if imageData.y < 600 then
			love.graphics.draw(image, imageData.x, imageData.y, imageData.r, 0.5, 0.5, 256/2, 256/2)
		else
			image:release()
			ImagesToDraw[image] = nil
			currentCoins = currentCoins - 1
		end
	end
end
Deletion won't work with this code. Now ImagesToDraw has numeric indices, therefore setting ImagesToDraw[image] to nil has no effect. Furthermore, even if you change it to read `ImagesToDraw[i] = nil`, there's the problem that ipairs() will stop at the first nil. For example, if an array has 12 elements and you set the 3rd one to nil, then ipairs() will only "see" the first two elements.

I think that the proper thing to do here is using table.remove() while looping manually. You can't use ipairs() to iterate while deleting, because table.remove() will shift indices and might skip elements if you increase the index and remove from the table in the same iteration.

So, the proper drawImages() function would be something like:

Code: Select all

local function drawImages()
        local i = 1
        while i <= currentCoins do
                local imageData = ImagesToDraw[i]
                [...]
                if imageData.y < 600 then
                        love.graphics.draw(image, ...)
                        i = i + 1
                else
                        image:release()
                        table.remove(ImagesToDraw, i)
                        currentCoins = currentCoins - 1
                end
        end
end
(note: untested)
User avatar
Bobble68
Party member
Posts: 162
Joined: Wed Nov 30, 2022 9:16 pm
Contact:

Re: How to force draw an image on top of another image?

Post by Bobble68 »

pgimeno wrote: Sun Mar 31, 2024 8:33 pm
Deletion won't work with this code. Now ImagesToDraw has numeric indices, therefore setting ImagesToDraw[image] to nil has
Right, yeah I didn't notice that. The way I usually handle deletions is by giving each of the objects a remove flag, and then at the end of update checking which objects need removing (probably not the fastest way to do it but it works for me)
Dragon
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: How to force draw an image on top of another image?

Post by pgimeno »

You can't reliably use ipairs() in that circumstance either. You have to either loop backwards, which ipairs() doesn't do, or manually advance the index or decrease the count as appropriate, like the loop I posted does.
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 4 guests