How many sprites can you have on screen before slowdown?

Yeah 200 should not be an issue. What kind of calculations are you doing in your update?
In this bunnymark benchmark with different method of moving sprites you can see what slows down the engine

2 Likes

thanks very interesting. This certainly can cope with the numbers.
Would it be the tilemap collisions? My game is getting no where near this framerate. I’ll check out the bunny code to see what I’m missing.

Lot’s of questions and hard to know exactly what’s wrong, but from your questions it’s possibly to deduce some information about your current implementation and guess what’s wrong.

You can have many thousands of sprites on the screen at the same time:

https://britzl.github.io/Bunnymark/

This tells me that you have a script on each zombie and a collision object too. This will at least slow things down, but I would not have guessed that it struggle that much.

Some thoughts:

It may seem nice and simple to have a script on each zombie but it is in fact quite inefficient. All of the zombies seems to behave the same way and move towards the player. I would suggest that you do not have a script on each zombie, but rather a single zombie AI script that keeps a list of all zombies. This script can get the player position and in a loop move all zombie game objects towards the player. This will be a lot more efficient. You could even decide to not update all zombies every frame and instead spread the work over multiple frames and move using go.animate() instead of manually each frame.

But what about collisions with walls? Easy! Assign a script to the game object holding the tilemap+collision shape and resolve zombie collisions in this script. The advantage is again that you have a single script instead of one per zombie.

As a future optimisation and to make the zombies smarter you could use a flood fill algorithm and use the data to reverse search your way back from any position to the player and use this to move all zombies and avoid obstacles.

3 Likes

Thanks - your “thoughts” are in the right direction I think.
I’m spawning my zombies - so yes the code gets executed for each zombie each time. So 200 executions. I’ll take a more in-depth look at the bunnies and Go.Animate to try to understand what I’m doing wrong.

I don’t fully understand how to go the collisions in the game object, but I will investigate that too.

Thanks for the help so far - everyone. At least I know I’m not going mad - the Engine can do what I need, I just have to code it correctly :slight_smile:

Can you share a screenshot of what the profiler shows?

1 Like

2 Likes

Oof, that’s a hefty frame.

Well there you have it. Your zombie script is taking waaay to long somehow. 199 script update calls are taking 48ms?! Even if that’s got other stuff included, the 13ms for 125 contact_point_responses is crazy.

Can you post your zombie.script here?

4 Likes

Here is the zombie script.
zombie.script (2.4 KB)

1 Like

Thats’ a good idea. I did some work and can see that it isn’t the drawing that’s causing the issue. It certainly is something to do with this zombie code. I’ve tried a few variants but don’t seem to be able to find it.
The Zombie code is created from a spawner. This doesn’t seem to be causing any issues from what I can see in the profiler. I can see the zombie collision code also coming up the ranks with heavy usage.

1 Like

Hmm…we’ll that’s very weird. There’s barely anything there. I wouldn’t think any of that would be a problem.

Reposting the contents here so it’s easier for others to check:

zombie.script
--[[
This is the code we will implment for the basic Zombie

we will need Zombie AI code as well for swarming
and zombie code fo each of the different types

From Main

ZOMBIE_TYPE_BASIC = 1
ZOMBIE_TYPE_STRONGER = 2
ZOMBIE_TYPE_BIG = 3
ZOMBIE_TYPE_GIANT = 4
--]]


--[[
Game space is 60x40 tiles

We need to randomly spawn a zombie in this space.
We need to check if the space is a valid tile. Otherwise the zombie will get stuck on the tile.
We need a function to check x,y against a tilespace

--]]

HIT_ID =  hash("contact_point_response")
WALL_ID = hash("Wall")

go.property("type", hash("ZOMBIE_TYPE_BASIC"))
go.property("velocity", vmath.vector3())


function init(self)

	self.correction = vmath.vector3()
	msg.post("/spawner#spawner", "zombie_created")

end


function update(self, dt)
	--test AI for the moment
	--Why so slow?

	local startpos = go.get_position()
	local offset = vmath.vector3()

	--using 0 during debug mode
	offset.x = 0 --math.random(-32, 32) -- random target close by
	offset.y = 0 --math.random(-32, 32) -- random target close by

	local target =go.get_position("/player")  + offset
	local dir = vmath.normalize(target - startpos)

	--we are trying to chase the player
	local p = go.get_position() + dir + self.velocity * dt

	go.set_position(p)


	--Make sure I face the right way
	sprite.set_hflip("#sprite", dir.x < 0) -- face the direction i', moving in

	--used for wall hits
	self.correction = vmath.vector3()

end

function on_message(self, message_id, message, sender)

	--[[ Zombie Collision Detection --]]

	-- Check if I hit a wall

	--if message_id == hash("contact_point_response") and message.group == hash("Wall") then
	if message_id == HIT_ID and message.group == WALL_ID then
		-- Get the info needed to move out of collision. We might
		-- get several contact points back and have to calculate
		-- how to move out of all of them by accumulating a
		-- correction vector for this frame:
		if message.distance > 0 then
			-- First, project the accumulated correction onto
			-- the penetration vector
			local proj = vmath.project(self.correction, message.normal * message.distance)
			if proj < 1 then
				-- Only care for projections that does not overshoot.
				local comp = (message.distance - message.distance * proj) * message.normal
				-- Apply compensation
				go.set_position(go.get_position() + comp)

				-- Accumulate correction done
				self.correction = self.correction + comp
			end
		end
	end


end

You are creating a few extra vectors in your update(), which you could avoid, but I really don’t think that’s the issue.

Is the game consistently running slowly every time you run it?
Did you try testing a bundled version instead of running from the editor?
Is it consistently running slower as you add more zombies?
I assume you checked if something else was running on your computer at the same time?
Did you try the bunnymark? The “Bunnymark using one update() per object” scene should be similar to what you’re doing (only 1024 scripts insetad of 200).

1 Like

I created an example project to show this:

HTML5: https://britzl.github.io/zombies-follow

4 Likes

Thanks,. yes I’ve been working with Bunnymark today - single update code.
I had to rewrite the zombie script using this method and it certainly has improved things.
I’m getting close to 22+ FPS from what i can see now, but i still have to add back the collision detection.
At 100 zombies it runs at a very decent speed.

You’re correct on the extra vectors too. I removed them and made the tracking code simpler, using p.x and p.y references instead. This has helped too.

I’ve been playing around with a number of things today. I’ll check again tomorrow. It is certainly getting faster.

PS. The speed is pretty consistent - so its not my PC on any background tasks from what I can see.
Will post an update to everyone tomorrow again after I’ve pushed my brain a bit further. Thank you for your help - you guys have been amazing.

1 Like

Thanks I will check this out. You guys are just amazing. I’ve made progress already with your first suggestion to check out bunnymark and I’ve speed improvements close to the single update version of this i think. I changed the code to update all the zombies in a single loop now. That has dramatically increased the speed. I’ll review your new code and see what I can improve from this.
Thank you

2 Likes

you can take a look this demo @Insality made and then @d954mas and I made some modifications. https://github.com/Insality/crowd-runner-defold

Tbh I dunno why in your case it’s so slow, I would like to take a look the whole project and play with it, not just one script.

6 Likes

Thanks. I’ve managed to get this working now!. Yippee.
I’ve happily got 400 zombies running around at full speed bumping into everything.

Thanks for everyone for all your help.

A number of factors …

  • Better code to update zombies.

  • Update all at once, instead of calling one at a time.

  • Optimized and deleted redundant code.
    *Thanks for the suggestions on the profiler - to see where the problems lay. I found a double call to the zombie scripts.
    *using p.x and p.y instead of calculating with vectors all the time. This helped speed up the ai.

  • updating all zombies at one, I only need to get the player position once, instead of 200.
    *reviewing the code for bunnies (single_update) really helped. Bunnymark 1.0

  • Found running in the debugger was slower on my PC. When I compiled a live version in HTML5 it runs at full speed all the time. So memory on my machine was an issue as well.

Thanks @britzl for all the code samples and advice. I will be digging deeper tomorrow into the demo you created. I need to rebuild my code from the ground up to remove any redundant calls etc.

9 Likes

… geometry wars is only one step away :slight_smile:

3 Likes
for i=1,#self.zombies do
		local id = self.zombies[i]
		local zp = go.get_position(id)

Calling go.get_position() in a loop is not great because it creates multiple short-lived vector objects, right? Is there any alternative to this currently?

1 Like

You’re correct. One thing you could do is to create one v3 per zombie when the zombies are spawned and reuse the same v3 every frame. Something like this:

function init(self)
	math.randomseed(os.time())
	self.zombies = {}
	for i=1,200 do
		local x = math.random(1, 800)
		local y = math.random(1, 480)
		local p = vmath.vector3(x, y, 0)
		local id = factory.create("#factory", p)
		self.zombies[#self.zombies + 1] = {
			id = id,
			pos = p
		}
	end
end

function update(self, dt)
	local pp = go.get_position("/player/player")
	for i=1,#self.zombies do
		local zombie = self.zombies[i]
		local id = zombie.id
		local zp = zombie.pos
		local dir = vmath.normalize(pp - zp)
		zp = zp + dir * 10 * dt
		go.set_position(zp, id)
	end
end

I also recall that someone on Discord shared an extension that optimized for this scenario by working on the v3 components directly.

1 Like

And what about the case where the position is changed extraneously to the object? For example through physics.

I will also try to find the extension thanks!

Maybe go.get_position() could have an overload that accepts a vector reference as an argument to store the result.

This is correct. I made a similar optimisation to my original code too.

I was also thinking if it might make sense to have a global variable for the player position - if it was to be monitored by other routines? In this case it’s a single zombie factory. what would happen if I had other factories? They too could share the information? Although the best option would be to use a single update function for all factories.