Below is a section of the pathfinding script inside of the NPC where it chases the player:
function chase()
while true do
npchumanoid.WalkSpeed = 20
npchumanoid.JumpPower = 50
if not walking and not game.Players:GetPlayerFromCharacter(script.Parent) then
for i,v in pairs(workspace:GetChildren()) do
if not v:findFirstChild("Zombie AI") and v:findFirstChildOfClass("Humanoid") and v:findFirstChild("Head") then
if (v:findFirstChild("Head").Position - npc.Head.Position).magnitude < sight then
canrandomwalk = false
local thehumanoid = v:findFirstChildOfClass("Humanoid")
local pathfinding = false
local thehead = v:findFirstChild("Head")
while (thehead.Position - npc.Head.Position).magnitude < sight and thehumanoid.Health > 0 and not v:findFirstChild("Zombie AI") do
npchumanoid.WalkSpeed = 20
npchumanoid:MoveTo(thehead.Position, thehead)
local path = game:GetService("PathfindingService"):FindPathAsync(torso.Position, thehead.Position) --find the path from scp's torso to victims head
local waypoints = path:GetWaypoints() --get the every point of the path
if path.Status == Enum.PathStatus.Success then
for q,w in pairs(waypoints) do --for every point existing..
if q ~= 1 then
local allow = 0
npchumanoid:MoveTo(w.Position, thehead) --...walk to it
while (torso.Position - w.Position).magnitude > 3.8 and allow < 20 do
allow = allow + 1
game:GetService("RunService").Heartbeat:wait()
end
if w.Action == Enum.PathWaypointAction.Jump then
npchumanoid.Jump = true
end
if thehumanoid.Health <= 0 then
break
end
if v:findFirstChild("Zombie AI") then
break
end
end
end
for q,w in pairs(npc:GetChildren()) do
if w.Name == "pospart" then
w:destroy()
end
end
else
npchumanoid:MoveTo(thehead.Position, thehead)
end
wait()
end
canrandomwalk = true
else
canrandomwalk = true
end
end
end
end
wait()
end
end
This is not the solution to your NPC pathfinding, but I would like to make your script more reliable and easier to understand:
Since you’re using a loop to find “Zombie AI”, CollectionService will help make it easier to find all of them. For every “Zombie AI”, insert a tag from the properties window and name the tag something like “Zombie”.
In your script, you can get a table of your zombies with this method:
local CollectionService = game.CollectionService
local Zombies = CollectionService:GetTagged("Zombie")
You have 5 different loops nesting within the same function. This is generally a bad practice, I don’t recommend nesting different loops because it’s not performant and reliable. If the PathfindingService runs into an error with the Zombie’s humanoid, your entire script may yield and stop running; something we don’t want for multiple AI. My suggestion is to use RunService’s Heartbeat event and going through the zombie’s functions step by step. I recommend that you read through this carefully and try to understand how the structure really works:
local CollectionService = game.CollectionService
local Zombies: {Model} = CollectionService:GetTagged("Zombie")
local function findTarget()
local target = nil
-- Your code to getting the target
return target
end
local function moveZombie(zombie: Model, target: Player)
if target then
-- Your code to moving the zombie to the target.
else
-- Your code to moving the zombie, if there is no target.
end
end
local function attackTarget(zombie: Model, target: Player)
if target:DistanceFromCharacter(zombie.PrimaryPart.Position) < 10 then
-- Your code to damaging the target when the zombie is in range.
end
end
local function onHeartbeat()
for i,zombie in Zombies do
local target = findTarget()
if target then
moveZombie(zombie, target)
attackTarget(zombie, target)
else
moveZombie(zombie)
end
end
end
local RunService = game["Run Service"]
local elapsed = 0
RunService.Heartbeat:Connect(function(deltaTime: number)
-- deltaTime is the time that passes on a heartbeat.
-- With elapsed and deltaTime, it will act as a 'wait' for 0.2 seconds.
-- This way, your code will run
elapsed += deltaTime
if elapsed < 0.2 then return end
elapsed = 0
onHeartbeat()
end)
In your original code, you’re obtaining services like PathfindingService and RunService, which can make your script even slower because you’re constantly calling GetService(). I suggest that you store a variable in your script that refers to these services.
For every iteration that your loop runs through, you’re changing the properties of the zombie, as well as creating variables in your loops. This will slow down your game and your script, as you’re constantly assigning different memory in your computer to different variables. I recommend that you store them outside of the loop, like so:
local CollectionService = game.CollectionService
local Zombies: {Model} = CollectionService:GetTagged("Zombie")
local ZombieInfos = {}
local function getZombieInfo(zombie: Model)
local ZombieInfo = {
Humanoid = zombie:FindFirstChildWhichIsA("Humanoid"),
Head = zombie:FindFirstChildWhichIsA("Head"),
Target = nil,
AttackRange = 8,
-- You can add more variables here, such as animation ids or different walkspeeds.
}
return ZombieInfo
end
local function setupZombies()
for i,zombie in Zombies do
ZombieInfos[zombie] = getZombieInfo(zombie)
print(ZombieInfos[zombie].AttackRange) -- Outputs 8.
end
end
I hope that this helps not only you, but to anyone else who happens to be looking for better AI scripting. I’m aware that this may not be the best way to handle AI, but it can be a good start for someone who’s interested in AI scripting.