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)
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
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
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.
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