NPC movement starts to lagging when there is too many target

Hello people!

I have an ai script that controll multiple zombies at the same time, it works perfectly fine, but there is a problem, since there are many targets in the workspace, the ai starts to get confuse and trying to change the zombie route every the interval come.

Here is my code:

local CollectionService = game:GetService("CollectionService")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")

local zombieTag = "zombie"
local updateInterval = .5
local maxPathComputeDistance = 50
local activeZombies = {}

local function findClosestTarget(zombie)
	local zombieRoot = zombie:FindFirstChild("HumanoidRootPart")
	if not zombieRoot then return end

	local closestTarget, closestDistance = nil, math.huge
	for _, model in pairs(workspace:GetChildren()) do
		if model:IsA("Model") and model ~= zombie and model:FindFirstChild("Humanoid") then
			local targetRoot = model:FindFirstChild("HumanoidRootPart")
			if targetRoot and model.Name ~= zombie.Name then
				local distance = (zombieRoot.Position - targetRoot.Position).Magnitude
				if distance < closestDistance and distance <= maxPathComputeDistance then
					closestDistance = distance
					closestTarget = model
				end
			end
		end
	end
	return closestTarget
end

local function moveZombie(zombie, path)
	local humanoid = zombie:FindFirstChild("Humanoid")
	if not humanoid then return end

	for _, waypoint in ipairs(path:GetWaypoints()) do
		humanoid:MoveTo(waypoint.Position) 
		if waypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true  
		end

		local reached = humanoid.MoveToFinished:Wait()
		if not reached or humanoid.Health <= 0 then
			break
		end
	end
end

local function updateZombie(zombie)
	local humanoid = zombie:FindFirstChild("Humanoid")
	local zombieRoot = zombie:FindFirstChild("HumanoidRootPart")
	if not humanoid or not zombieRoot or humanoid.Health <= 0 then
		CollectionService:RemoveTag(zombie, zombieTag)
		activeZombies[zombie] = nil
		return
	end

	local target = findClosestTarget(zombie)
	if target then
		local targetRoot = target:FindFirstChild("HumanoidRootPart")
		if targetRoot then
			local path = PathfindingService:CreatePath({
				AgentRadius = humanoid.HipHeight,
				AgentHeight = 5,
				AgentCanJump = false,
			})

			path:ComputeAsync(zombieRoot.Position, targetRoot.Position)
			if path.Status == Enum.PathStatus.Success then
				moveZombie(zombie, path)
			end
		end
	end
end

local function onZombieAdded(zombie)
	if activeZombies[zombie] then return end
	activeZombies[zombie] = true

	for i,v in pairs(zombie:GetDescendants()) do
		if v:IsA("BasePart") and v:CanSetNetworkOwnership() then
			v:SetNetworkOwner(nil)
		end
	end
end

local function onZombieRemoved(zombie)
	activeZombies[zombie] = nil
end

local function manageZombies()
	while true do
		for zombie, _ in pairs(activeZombies) do
			task.spawn(updateZombie, zombie)
		end
		task.wait(updateInterval)
	end
end

local function onChildAdded(child)
	if child:IsA("Model") and CollectionService:HasTag(child, zombieTag) then
		onZombieAdded(child)
	end
end

local function onChildRemoved(child)
	if child:IsA("Model") and activeZombies[child] then
		onZombieRemoved(child)
	end
end

workspace.ChildAdded:Connect(onChildAdded)
workspace.ChildRemoved:Connect(onChildRemoved)

for _, child in pairs(workspace:GetChildren()) do
	if child:IsA("Model") and CollectionService:HasTag(child, zombieTag) then
		onZombieAdded(child)
	end
end

task.spawn(manageZombies)
External Media

I hope you guys can help me out to solve this problem!

1 Like

you can assign an attribute to any zombie that already have a target then before giving the zombie a target check if the zombie already have a target or not

I use runservice heartbeat and seems to fix it, but when i spawn many of them they just stopped respond, do you know how to fix that

1 Like

is it giving any errors in the output ?

no errors given in the output, they just stood there and do nothing even i killed some of them

1 Like

did they stop before using heartbeat ?

i think so, spawned them and they just froze

1 Like

is the zombies spawn during run time or they are already set up ?

I place them in replicate storage and spawn them by clone them to workspace, and they will be set up when added to workspace

1 Like

try this code :-

local CollectionService = game:GetService("CollectionService")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")

local zombieTag = "zombie"
local updateInterval = .5
local maxPathComputeDistance = 50
local activeZombies = {}
local activatedzombies = {}

local function findClosestTarget(zombie)
	local zombieRoot = zombie:FindFirstChild("HumanoidRootPart")
	if not zombieRoot then return end

	local closestTarget, closestDistance = nil, math.huge
	for _, model in pairs(workspace:GetChildren()) do
		if model:IsA("Model") and model ~= zombie and model:FindFirstChild("Humanoid") then
			local targetRoot = model:FindFirstChild("HumanoidRootPart")
			if targetRoot and model.Name ~= zombie.Name then
				local distance = (zombieRoot.Position - targetRoot.Position).Magnitude
				if distance <= maxPathComputeDistance then
					closestDistance = distance
					closestTarget = model
				end
			end
		end
	end
	return closestTarget
end

local function moveZombie(zombie, path)
	local humanoid = zombie:FindFirstChild("Humanoid")
	if not humanoid then return end

	for _, waypoint in ipairs(path:GetWaypoints()) do
		humanoid:MoveTo(waypoint.Position) 
		if waypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true  
		end

		local reached = humanoid.MoveToFinished:Wait()
		if not reached or humanoid.Health <= 0 then
			break
		end
	end
	return true
end

local function updateZombie(zombie)
	local humanoid = zombie:FindFirstChild("Humanoid")
	local zombieRoot = zombie:FindFirstChild("HumanoidRootPart")
	if not humanoid or not zombieRoot or humanoid.Health <= 0 then
		CollectionService:RemoveTag(zombie, zombieTag)
		activeZombies[zombie] = nil
		return
	end

	local target = findClosestTarget(zombie)
	if target then
		local targetRoot = target:FindFirstChild("HumanoidRootPart")
		if targetRoot then
			local path = PathfindingService:CreatePath({
				AgentRadius = humanoid.HipHeight,
				AgentHeight = 5,
				AgentCanJump = false,
			})

			path:ComputeAsync(zombieRoot.Position, targetRoot.Position)
			if path.Status == Enum.PathStatus.Success then
				local resuelt = moveZombie(zombie, path)
				table.remove(activatedzombies , zombie)
			end
		end
	end
end

local function onZombieAdded(zombie)
	if activeZombies[zombie] then return end
	activeZombies[zombie] = true

	for i,v in pairs(zombie:GetDescendants()) do
		if v:IsA("BasePart") and v:CanSetNetworkOwnership() then
			v:SetNetworkOwner(nil)
		end
	end
end

local function onZombieRemoved(zombie)
	activeZombies[zombie] = nil
	activatedzombies[zombie] = nil
end

local function onChildAdded(child)
	if child:IsA("Model") and CollectionService:HasTag(child, zombieTag) then
		onZombieAdded(child)
	end
end

local function onChildRemoved(child)
	if child:IsA("Model") and activeZombies[child] then
		onZombieRemoved(child)
	end
end

for _, child in pairs(workspace:GetChildren()) do
	if child:IsA("Model") and CollectionService:HasTag(child, zombieTag) then
		onZombieAdded(child)
	end
end

workspace.ChildAdded:Connect(onChildAdded)
workspace.ChildRemoved:Connect(onChildRemoved)

while true do
	for zombie, _ in pairs(activeZombies) do
		if table.find(activatedzombies , zombie) then
			return
		end
		table.insert(activatedzombies , zombie)
		task.spawn(updateZombie, zombie)
	end
	task.wait(updateInterval)
end

save the orignal code because this may not work

1 Like

does it work ?
Weird text because of a Weird limit

For NPCs, I always replicate them to their own folder in workspace. When targeting player characters, I use the following:

-- FIND NEAREST PLAYER
local function findTarget()
	local maxDist = maxSearchDist
	for _, player in ipairs(players:GetChildren()) do
		if player.Character then
			local targetHRP = player.Character:FindFirstChild("HumanoidRootPart")
			if not targetHRP then return end
			local distance = (keeperHRP.Position - targetHRP.Position).Magnitude

			if distance < maxDist then
				targetPlayer = player
				maxDist = distance
			end
		end
	end
	return targetPlayer
end

Why? Well, so we don’t have to loop through every Child in the workspace, but selectively target the player characters only. I have seen some experiences with very messy workspaces.

1 Like

sadly this doesnt, it got an error invalid argument #2 to ‘remove’ (number expected, got Instance).

i guess i have no choices then… ended up to define the target as the players only, but still i really wanted them to chase non player humanoids.

1 Like

Also ive added a waypoints marker, and it seems like the zombie is creating a bunch of weird waypoints on they way to the target, these points surround the zombie and causing it to move crazy

1 Like

sorry for that , could you send the new version of the script ?

you do not have to make them only chase players you can just put all of the others NPC is a folder in the workspace

Ive rewritten it and no longer use pathfinding waypoints and instead make them chase the target directly, thanks for the help!

1 Like

no problem ! , but this may cause issues like trying going through walls

1 Like

i know it, the zombies cant even unstuck themselves but i have no choice tho

1 Like