Zombies are buggy, slow to notice players, and upon spawn cause a lag spike. Any ideas why?

So, I’m trying to make a game that is based off of PenguinDoc9999’s Survive in Area 51, and a big part of that is having many enemies for players to kill. Issue is, as the title says, the zombies (only enemy for now) are buggy. Sometimes you can even get them to follow you without attacking, for some reason. Admittedly, the code is based off of a tutorial video’s zombie script.

The zombies run off of a centralized ModuleScript in ServerScriptService, and are all R6 to reduce part-count lag.

Here’s a testing place:
First Person Area 51 testing.rbxl (1.1 MB)

Here’s the ModuleScript, for people who can’t/don’t want to go in the place file:

local AI = {}

local ammo = game:GetService("ServerStorage"):WaitForChild("Ammo"):GetChildren()
local ammoNum = #game:GetService("ServerStorage"):WaitForChild("Ammo"):GetChildren()

function AI.Register(entity)
	--print(entity.Name)
	--print("registered")
	--body parts
	local hitDB = false
	local isMoving = false
	local willRandomMove = false
	local myHuman = entity:WaitForChild("Humanoid")
	local myAnimator = myHuman:WaitForChild("Animator")
	local myRoot = entity:WaitForChild("HumanoidRootPart")
	local myHip = entity:WaitForChild("Hip")
	--animations
	local grab = entity:WaitForChild("Grab")
	local grabAnim = myAnimator:LoadAnimation(grab)
	grabAnim.Priority = Enum.AnimationPriority.Action
	--sounds
	local grabSound = myRoot:WaitForChild("Attack")
	local hitSound = entity:WaitForChild("Right Arm").ZombHit

	local function checkSight(target)
		local ray = Ray.new(myRoot.Position, (target.Position - myRoot.Position).Unit * 40)
		local hit, pos = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})
		if hit then
			if hit:isDescendantOf(target.Parent) and math.abs(hit.Position.Y - myRoot.Position.Y) < 10 then
				return true
			end
		end
		return false
	end

	local function findTarget()
		local dist = 60
		local target = nil
		local potTargets = {}
		local seeTargets = {}
		for i, v in ipairs(workspace:GetChildren()) do
			local human = v:FindFirstChild("Humanoid")
			local torso = v:FindFirstChild("HumanoidRootPart")
			if human and torso then
				if (myRoot.Position - torso.Position).magnitude < dist and human.Health > 0 then
					table.insert(potTargets, torso)
				end
			end
		end
		if #potTargets > 0 then
			for i, v in ipairs(potTargets) do
				if checkSight(v) then
					table.insert(seeTargets, v)
				elseif #seeTargets < 1 and (myRoot.Position - v.Position).magnitude < dist then
					target = v
					dist = (myRoot.Position - v.Position).magnitude
				end
			end
		end
		if #seeTargets > 0 then
			dist = 60
			for i, v in ipairs(seeTargets) do
				if (myRoot.Position - v.Position).magnitude < dist then
					target = v
					dist = (myRoot.Position - v.Position).magnitude
				end
			end
		end

		-- Check if target is still visible
		if target and not checkSight(target) then
			--print("oh, guess not")
			target = nil
		end

		return target
	end

	local function attack(target)
		coroutine.resume(coroutine.create(function()
			if (myRoot.Position - target.Position).Magnitude < 3 then
				if hitDB == false then
					hitDB = true
					grabSound:Play()
					grabAnim:Play()
					grabAnim.Stopped:Wait()
					if target.Parent ~= nil then
						if (myRoot.Position - target.Position).Magnitude < 3 then
							hitSound:Play()
							target.Parent.Humanoid:TakeDamage(10)
						end
					end
					task.wait(0.3)
					hitDB = false
				end
			end
		end))
	end

	local function findPath(target)
		local path = game:GetService("PathfindingService"):CreatePath()
		path:ComputeAsync(myRoot.Position, target.Position)
		local waypoints = path:GetWaypoints()

		if path.Status == Enum.PathStatus.Success then
			for _, waypoint in ipairs(waypoints) do
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					myHuman.Jump = true
				end
				myHuman:MoveTo(waypoint.Position)
				local timeout = myHuman.MoveToFinished:Wait(1)
				if not timeout then
					--print("Stuck :(")
					myHuman.Jump = true
					findPath(target)
					break
				end
				if checkSight(target) then
					repeat
						myHuman:MoveTo(target.Position)
						--attack(target)
						--print((myRoot.Position - target.Position).Magnitude)
						if (myRoot.Position - target.Position).Magnitude < 3 and hitDB == false then
							print("hit")
							hitDB = true
							grabSound:Play()
							grabAnim:Play()
							local damageEntity = task.delay(grabAnim.Length, function()
								if target.Parent ~= nil then
									if (myRoot.Position - target.Position).Magnitude < 3 then
										hitSound:Play()
										target.Parent.Humanoid:TakeDamage(10)
										task.wait(0.3)
										hitDB = false
									end
								end
							end)
						end
						task.wait(0.1)
						if target == nil then
							break
						elseif target.Parent == nil then
							break
						end
					until checkSight(target) == false or myHuman.Health < 1 or target.Parent.Humanoid.Health < 1
					break
				end
				if (myRoot.Position-waypoints[1].Position).magnitude > 20 then
					findPath(target)
					break
				end
			end
		end
	end

	local function walkRand()
		local xRand = math.random(-10, 10)
		local zRand = math.random(-10, 10)
		local goal = myRoot.Position + Vector3.new(xRand, 0, zRand)

		local path = game:GetService("PathfindingService"):CreatePath()
		path:ComputeAsync(myRoot.Position, goal)
		local waypoints = path:GetWaypoints()

		if path.Status == Enum.PathStatus.Success then
			for _, waypoint in ipairs(waypoints) do
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					myHuman.Jump = true
				end
				myHuman:MoveTo(waypoint.Position)
				local timeout = myHuman.MoveToFinished:Wait(.5)
				if not timeout then
					--print("Stuck :(")
					myHuman.Jump = true
					walkRand()
				end
			end
		else
			--print("Path failed :(")
			task.wait(.5)
			walkRand()
		end
	end

	myHuman.Died:Connect(function()
		local drop = ammo[math.random(ammoNum)]:Clone()
		drop.Position = myRoot.Position
		drop.Parent = game.Workspace
		game:GetService("Debris"):AddItem(drop,10)
		game:GetService("Debris"):AddItem(entity,1)
	end)

	myHip.Touched:Connect(function(obj)
		if not obj:FindFirstAncestorWhichIsA("Model"):FindFirstChild("Humanoid") and obj:FindFirstAncestorWhichIsA("Model").Parent:FindFirstChild("Humanoid") then
			myHuman.Jump= true
		end
	end)

	coroutine.wrap(function()
		while true do
			task.wait(math.random(1, 7))
			willRandomMove = true
		end
	end)()

	while task.wait() do
		if myHuman.Health < 1 then
			break
		end
		local target = findTarget()
		if target then
			--print("OWO what's this?")
			myHuman.WalkSpeed = 16
			findPath(target)
		else
			myHuman.WalkSpeed = 8
			if willRandomMove == true and isMoving == false then
				isMoving = true
				willRandomMove = false
				task.spawn(walkRand)
				isMoving = false
			end
		end
	end
end

return AI

If you have any idea of what the culprit could be, or if my npc code is just plain bad, please let me know. Thanks!

1 Like

could be a memory leak from not closing the coroutines after the enemy is dead

1 Like

How many Humanoids are we talking about? A lot of them will be laggy.
Another thing is that using pathfinding times out after about 8 seconds, so any movement will stop if the zombie doesn’t reach the waypoint in that time.

1 Like