So I am currently working on a huge project that relates to a zombie game. In this game we will be having a ton of moving zombies chasing players, and killing them. From what I currently have scripted, the zombie movement is similar to that of Zombie Attack or something similar.
Having potentially hundreds of moving zombies at once could pose the problem of optimization. The script that I currently have is probably very poorly optimized. After suplicating the zombie around like 30 to 50 times the game seems to struggle. What are some ideas or things that i could use to prevent this problem, but also still have the main functionality that is already in this script?
Let me know if you have any questions about this, and any help would be greatly appreciated!
Here is the current script, which is a child of the zombie rig itself.
local hum = script.Parent.Humanoid
local humRoot = script.Parent.HumanoidRootPart
local rs = game:GetService("RunService")
local function DetermineTarget()
local nearestTarget = nil
local nearestDistance = math.huge -- sets at a very larger number. use this to make sure its closer than last target
for i,v in game.Players:GetPlayers() do -- you can remove pairs completely and just pass a table and it will automatically determine which is better
local character = v.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then
local Distance = (character.HumanoidRootPart.Position - humRoot.Position).Magnitude
if Distance < nearestDistance then -- if the current player distance is less than the previous nearestDistance then set target
nearestTarget = v
nearestDistance = Distance -- remember to set the nearestdistance when you set a new target
end
end
end
return nearestTarget
end
while true do
local target = DetermineTarget()
if target then -- cleaned this to not use not...
hum:MoveTo(target.Character.HumanoidRootPart.Position, target.Character.HumanoidRootPart)
else
print("No target found")
end
task.wait() -- it is bad practice to put wait() as the condition for the while loop
end
Well, the first thing i will say is that using hum:moveto() has its restrictions because if the zombie doesn’t reach the target in 8 seconds, it will timeout and freeze in place. That could be part of your problem.
Also, the loop could be a problem. I know you used task.wait, but a while true loop in 50 different zombies could be potentially laggy.
Try those two things first, and if it is still lagging, let me know
Well, you could use Pathfindingservice and get all of the waypoints in the generated path, and moveto all of those. Waypoints aren’t that far spaced apart, and on average takes less than one second to move to them.
From my experience pathfindingservice is super glitchy. The zombie goes to where the player used to be, so when they walk it doesn’t update where he is walking. When using the second argument in moveto, it follows the player no matter where they move, and with pathfinding it doesn’t.
This post and subsequent discussion seems to be divided into ideas for optimizing the Zombie chasing/movement, and optimizing the AI behaviour (loop steps), so I thought I’d weigh in on the latter issue with an idea based on a system I’ve used in the past.
Main Idea
One way you might be able to save on performance by doing less work every loop update is to use two states for the zombie AI:
Scan: The zombie scans for the nearest Player to its location and assigns it as its target.
Pursue: The zombie pursues the selected Player target.
Instead of constantly scanning the distance from every Player every loop update, only do this once when needed to choose a Player as a target.
When a target is found, switch to the Pursue State. In this state, while the target Player is alive in-game and within a certain threshold distance, move the Zombie to that target player and don’t bother checking the distance of the remaining Players.
The added “threshold distance” suggestion would be to allow the zombie to change targets if the current target player gets too far away from the pursuing zombie. It wouldn’t look great if a zombie is pursuing a player 200 studs away with several other players nearby.
Limitations
The limitation of this solution is that the zombies will become fixed on pursuing a specific player and ignore other players for some time. In the worst-case, you could have most or all of a game’s zombies pursuing a single player, (though this is unlikely if you use a smaller threshold distance for abandoning a target, and/or the Player isn’t invincible).
Other Ideas
You could incorporate more ways for the zombie to abandon its current target and go after a new one, i.e. if the Zombie is chasing PlayerA and PlayerB starts attacking the Zombie, you could have the Zombie switch its target to PlayerB instead. (This would help overcome the limitation I mentioned).
You could use more states i.e. an idle state when no players are near the zombie when it scans. You could even adjust how frequently a zombie scans by the minimum distance of the closest player. i.e., if no players are nearby, wait longer until the next scan. (This again may improve performance by doing the scanning work less frequently).
Conclusion
Anyways, I’d argue that this divided behaviour approach would be a bit more performant because when the zombie is pursuing a specific Player, you don’t have to bother checking distances for other players.
I won’t say that this will totally solve your performance issues, but it may be able to help it a bit.
hum:moveto() isn’t the best way to make the zombie move, I would suggest removing while true do, also maybe use a pathfinding system, I think you would be able to find one in the resources topic. if you don’t I don’t know what else to say.
I do credit The Carl_Weezer guy for some of the suggestions ofcourse
I had encountered a similar optimization issue in one of my projects. If you don’t mind the zombies AI being effectively stateless, an easy solution is to use flow field pathfinding. This allows O(n) scaling of zombies, up to the thousands even without any noticable lag from your own code.
One noticable pitfall of this solution is flow fields effectively limit your zombies to a grid, and if a zombie is too close to a player or your map has especially small details, the grid may prevent the zombies from moving to a desired location. I personally had to write two pathfinding implementations when using this approach - one for far pathfinding where it doesn’t matter if the NPC’s movements are off by a couple studs, and one for close pathfinding where accuracy and timing is critical.
That’s exactly why you need to modify every pathfinding system even if it was the smallest flaw, also very annoying and devforums will barely help. that’s an issue on the devforums for me, it doesn’t trust people enough until they spend 4 hours, I don’t trust premade code to be honest, but some code I can trust. also is good advice, might use it myself at one point.