Zombies!. I’m developing a zombie game, - think “robotron” for older devs. The objective is to have a scrollable tilemap (2 screens wide x 2 high"). Upto 200 Zombies will be on active / onscreen at any one time.
I’ve a demo developed but the engine is “crawling” to display and update. It’s really struggling.
Can the Defold engine support what I want? or is my code wrong?
I’ve a tile map with collisions set for walls against the zombies and player.
Tiles are 32x32, sprites are similar size.
Tile map is about 60 tiles x 40.
Is the number of collisions causing the slowdown? or it just purely the number of sprites?
Is the processing required too heavy?
I’ve tried the profiler, but can’t see the main culprit. It looks like the main gameloop/render is killing the time - but I’m relatively new to defold so I don’t know what that means.
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
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:
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.
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
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.
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.
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).
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.
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
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.