Optimize Zombie AI

i recently wrote my own very basic like Zombie AI where the only thing it does is follow the closest player using the function :MoveTo and allowing to have some sounds, however while it is functional, if i try to put in more zombie AI, it ends up starting to lag more and more, at least 30 will cause the entire game to lag.
image

image
i’ve noticed that the amount of received data is ridiculously high after actually using the zombie AI, so i definitely know its coming from there. i use a runService loop RenderStepped to do some of this. (i get around 30 fps from playing this, which is a high decrease from 120)

so how do i optimize the zombie AI’s loop? i would appreicate some help.

I AM ALREADY AWARE OF PATHFINDINGSERVICE, HOWEVER I DO NOT WANT THE ZOMBIES TO BE THAT SMART. I ONLY WANT THE ZOMBIES TO JUST CONSTATNLY FOLLOW A PLAYER AND HAVE BASIC AI.

here’s some scripting i’ve done so far

function findClosestCharacter()
	local closestPlayer = nil
	local closestCharacter = nil
	local closestCharacterDistance = nil
	
	for _, player in players:GetPlayers() do
		if not player.Character then continue end
		
		local characterPosition = player.Character.PrimaryPart.Position
		local NPCPosition = character.PrimaryPart.Position
		
		local distance = (characterPosition - NPCPosition).Magnitude
		
		if not closestCharacter then
			closestPlayer = player
			closestCharacter = player.Character
			closestCharacterDistance = distance
			continue
		end
		
		if distance < closestCharacterDistance then
			closestPlayer = player
			closestCharacter = player.Character
			closestCharacterDistance = distance
		end
	end
	
	if closestPlayer and players:FindFirstChild(closestPlayer.Name) and closestPlayer.Character then
		target = closestCharacter
		character.PrimaryPart:SetNetworkOwner(closestPlayer)
	end
end

function moveToSpot(part)
	if part then
		humanoid:MoveTo(part.Position)
	end
end

function steppedLoopForAI(timeSinceConnected, deltaTime)
	tick_findNextPlayerTime += deltaTime
	tick_zombieSoundsTime += deltaTime

	if target then
		moveToSpot(target.PrimaryPart)
	end

	if tick_findNextPlayerTime > findNextPlayerTime then
		tick_findNextPlayerTime = 0

		findClosestCharacter()
	end

	if tick_zombieSoundsTime > zombieSoundsTime then
		tick_zombieSoundsTime = 0
		zombieSoundsTime = math.random(2, 5)

		playZombieSound()
	end
end 

How often do you run the steppedLoopForAI function?

What you will need to implement for these zombies to follow the players without lagging is multithreading.

Here is a helpful Youtube video:

2 Likes

If you want to improve bandwidth usage, here is undoubtedly one of my top 10# favorite threads of all time!
This post goes very in-depth on how you could optimize your network usage.

2 Likes

i run this function pretty much every frame, the function to check for the nearest player is done every 5 seconds but im pretty sure the :MoveTo function is whats causing a lot of the hassle of load

i’ll take a look at the video, thanks! from face value, it would basically separate the :MoveTo function into different threads (CPUs im thinking) so that it can reduce the load i’m guessing? i’ll update this soon with my attempt on implementing multithreading

this was an interesting post to read but it doesn’t exactly have all the things i’d want as the move function is i’m pretty sure the culprit for all the issues, i don’t actually move the parts through CFraming nor any of that, i just use a basic function although i will definitely keep this post for the future, thanks for sharing!

1 Like

new update if anyone can help:

it turns out :MoveTo function itself is not safe in parallel and from microprofiling my game i find that the time for physics simulation and worker stuff is pretty large:
image
image
(this is when i have like 50 zombies running at once)
i’m not sure on how to begin with fixing these long frame times, if anyone could help.

only mathematical operations can be done in parallel operations, it is not possible to do physical operations, including :MoveTo, because you are physically executing the npc. instead of scanning every frame, you can make the npc scan every second. this will only be to find the player. after detecting the player, follow the player with a loop until the player leaves the detection area. you can use repeat until or while for this. you can use a lower number instead of 1 second in the follow-up loop, for example, running the :MoveTo Function every 0.05 seconds.

2 Likes

most of the solutions here fixed my issue:

  • i multithreaded the scanning for closest players
  • also multithreaded the renderstepped loop itself until it has to do all the physical operations
  • disabled a bunch of humanoid states

eventually i’ll probably have to reimplement their movement (cause i wanna make a wave system) by controlling it through one server scripted loop instead which should help not run through 100 renderstepped loops at once

it doesn’t have the best performance with 100 zombies, but with 50 it’s pretty respectable so it’s good

the ragdolling system is pretty much the only issue where if theres too many of the “ragdoll death” script running at once it just starts lagging the player again

thanks guys! multithreading was def interesting to learn abt

1 Like