Page 1 of 2

How to draw all objects on screen?

Posted: Mon Nov 28, 2016 10:27 am
by LeNitrous
I'm currently making a prototype Visual Novel Engine.
What I'm looking for is to draw all objects on screen without having to call their draw function one by one. I'm using hump.class to handle my classes and this is what I got so far.

Code: Select all

function love.draw()
	actor1:draw()
	actor2:draw()
	actor3:draw()
end
Any help is appreciated. Thanks!

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 10:44 am
by Lafolie
Pop all your actors (or drawable actors) in a table and iterate over it, calling the relevant function. You can use this technique in many places and for many things. For example, updating all your actors could use the exact same table.

Code: Select all

actors = {} --this is our list of actors
love.load = function()
	table.insert(actors, player:new("Player 1", 16, 16)) --add a "player" actor to the table named 'Player 1, at x16:y16
end

love.update = function(dt)
	for n, actor in ipairs(actors) do
		actor:update(dt)
	end
end

love.draw = function()
	for n, actor in ipairs(actors) do
		actor:draw()
	end
end
This is a pretty basic example, obviously you need to consider how to 'skip' objects that don't get draw but how you do that is up to you. You could keep a separate table for drawing, a boolean property, check whether a draw function exists, or even something like only storing the functions themselves in the table (although this method has some caveats). Read up on polymorphism for really cool stuff you can do with tables of objects with common functionality.

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 11:53 am
by LeNitrous
Given above works well. Can it be more efficient by calling table.insert inside the object's init function instead of calling table.insert for every actor object?
Some psuedocode as it doesn't actually work but you'll get the gist of what I mean:

Code: Select all

-- inside actor.lua
actor = class{
	init = function(self, name, image)
		self.name	= name
		self.image	= image
	end

	table.insert(actors, self)
}

function actor:draw()
        -- some draw code here
end

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 1:36 pm
by Smoggert
^ in order for this to work you would have to send the table reference (actors) with each actor you create.
Running the insert manually feels less convoluted, unless you would make a "actorhandler" class and run it with that.

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 1:49 pm
by LeNitrous
I'll try to make an actor handler. For now I'll use this solution for prototyping.

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 2:13 pm
by Sir_Silver
Can it be more efficient by calling table.insert inside the object's init function instead of calling table.insert for every actor object?
Yes, but it probably won't matter too much how efficient your actor initialization and insertion is unless your going to be creating an absurd amount of them, or if you're creating many of them while the game is being played. I really like writing code examples so here's one showing how you can optimize table insertion.

Code: Select all

local test_table_1 = {}

local start = os.clock()

for i = 1, 1000000 do
	table.insert(test_table_1, i)  --Using table.insert 1,000,000 times.
end

print("Default use of table.insert time taken: ", os.clock() - start, "\n")

local test_table_2 = {}
local insert = table.insert

local start = os.clock()

for i = 1, 1000000 do
	insert(test_table_2, i)  --Using our localized version of table.insert 1,000,000 times.
end

print("Localized table.insert function time taken: ", os.clock() - start, "\n")

local test_table_3 = {}

local start = os.clock()

for i = 1, 1000000 do
	test_table_3[#test_table_3 + 1] = i  --Directly placing our values at the end of the table 1,000,000 times.
end

print("Direct insertion without function overhead time taken :", os.clock() - start, "\n")

--[[Default use of table.insert time taken: 	0.2400000000000002	
			
Localized table.insert function time taken: 	0.20999999999999996	
			
Direct insertion without function overhead time taken :	0.17999999999999972]]
This example shows that even with a million values to be inserted into a table, the difference between the normal way and the fastest way of inserting into a table yields only about 1/20th of a second gain. Pretty negligible for most application, but I think it's good practice to use this method when storing values in a table in love.update or love.draw

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 2:42 pm
by ivan
How about:

Code: Select all

local test_table_4 = {}

local start = os.clock()

local n = #test_table_4
for i = 1, 1000000 do
   test_table_4[n + i] = i  --Directly placing our values at the end of the table 1,000,000 times.
end
With multiple inserts, you don't need to get the table length (#) each time.
Note that n + i could be removed too with just:

Code: Select all

local n = #test_table_4
for i = n + 1, n + 1000000 do
   test_table_4[i] = i  --Directly placing our values at the end of the table 1,000,000 times.
end

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 2:44 pm
by Sir_Silver
Wow I'm surprised I've never tried to do it that way - nice work :)

Just came in here to edit it and say WOW massive optimization XD

Direct insertion without function overhead time taken : 0.2300000000000002 --This is how long my method took.
Ivan's way : 0.040000000000000036 --This is how long Ivan's method took.

Uh yeah wow... I feel embarrassed for even calling my method an optimization now. :emo:

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 2:56 pm
by ivan
Yep, "table.insert" is good when you want to put something in the front or middle of the table (incrementing all following indexes).
If you just want to append values to the end of the table then "#" is probably faster.

Re: How to draw all objects on screen?

Posted: Mon Nov 28, 2016 8:29 pm
by LeNitrous
Thanks for the ideas but I forgot to mention that each object should have a unique name. This is what my main.lua looks like:

Code: Select all

actors = {}

-- actor(name, position, isVisible)
Chris = actor(Chris, "center", true)
Tristan = actor(Tristan, "left", false)

function love.load()
	table.insert(actors, Chris)
	table.insert(actors, Tristan)
	Chris:speak("Hello there!")
	Tristan:show()
	Tristan:speak("Hi there!")
end

function love.draw()
	for n, actor in ipairs(actors) do
		actor:draw()
	end
end
Unless there's a roundabout way, it would be nice.