I am trying to make a custom NPC Pathfinding system for a game. The system works great… for the first 60 seconds or so. After, the NPC’s start to stutter and stop walking between points causing a stop-go-stop-go effect.
I’m unsure what would cause this as it works fine and after a short while it stutters as you can see below:

script.Name = script.Parent.Name.." AI"
_G.Debug = true
--//Config Values
local attackDamage = 20
local attackCooldown = 2
local searchDistance = 100
local wanderDistance = 20
local returntoWanderWhenAttackDB = false -- (i have a mob in my game that will attack once and run away)
--//Services
local pathFinding = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Path = pathFinding:CreatePath({AgentRadius = 4;WaypointSpacing = 5})
local Waypoints = {}
local CurrentPoint = 0
local BlockedPoint = 999
--//Vars
local Body = script.Parent
local Humanoid = Body:WaitForChild("Humanoid")
local Head = Body:WaitForChild("Head")
local bodyRoot = Body:WaitForChild("HumanoidRootPart")
local attackDB = false
local debugTable = {}
local function getPath(pointA,pointB)
CurrentPoint = 0
BlockedPoint = 999
Path:ComputeAsync(pointA,pointB)
Waypoints = Path:GetWaypoints()
if _G.Debug then
for _,v in pairs(debugTable) do v:Destroy() end;debugTable = {}
for _, waypoint in pairs(Waypoints) do
local part = Instance.new("Part")
part.Shape = "Ball"
part.Material = "Neon"
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position
part.Anchored = true
part.CanCollide = false
part.Parent = game.Workspace
table.insert(debugTable,part)
end
end
end
local function getClosestHRP()
local closestHRP,closestDistance = nil,searchDistance
if attackDB and returntoWanderWhenAttackDB then return nil end --If return to wander after attack is true then dont find humans
--//Find closest player within the range defined in searchDistance
for _,Player in pairs(Players:GetPlayers()) do
local pChar = Player.Character
if pChar and Players:GetPlayerFromCharacter(pChar) then
local pHRP = pChar:FindFirstChild("HumanoidRootPart")
local pHumanoid = pChar:FindFirstChild("Humanoid")
if pHRP and pHumanoid then
local pDistance = (pHRP.Position - bodyRoot.Position).Magnitude
if pDistance < closestDistance and pHumanoid.Health > 0 then
closestHRP = pHRP
closestDistance = pDistance
end
end
end
end
return closestHRP
end
Humanoid.Touched:Connect(function(hit,limb)
if not attackDB then
local pHumanoid = hit.Parent:FindFirstChild("Humanoid")
local pChar = hit.Parent
if pHumanoid and Players:GetPlayerFromCharacter(pChar) then
if pHumanoid.Health > 0 then
attackDB = true
Head:FindFirstChild("Attack"):Play()
pHumanoid:TakeDamage(attackDamage)
task.wait(attackCooldown)
attackDB = false
end
end
end
end)
Path.Blocked:Connect(function(p)
if p > CurrentPoint then
if _G.Debug then
print("Path Blocked at",p)
end
BlockedPoint = p
end
end)
while Humanoid.Health > 0 do
local closestHRP = getClosestHRP()
local goingDirect = false
if attackDB and returntoWanderWhenAttackDB then closestHRP = nil end --On attack cooldown
if closestHRP then
local ray = Ray.new(bodyRoot.Position, (closestHRP.Position - bodyRoot.Position).Unit * 100)
local hit,position = workspace:FindPartOnRayWithIgnoreList(ray,{script.Parent})
if hit then --If can see player and they are at a similar y pos then go direct
if hit:IsDescendantOf(closestHRP.Parent) and math.abs(bodyRoot.Position.Y - closestHRP.Position.Y) < 4 then
Humanoid:MoveTo(position)
goingDirect = true
task.wait()
end
end
if not goingDirect then
getPath(bodyRoot.Position or Humanoid.WalkToPoint,closestHRP.Position)
end
else
local wanderPos = bodyRoot.Position + Vector3.new(math.random(-wanderDistance,wanderDistance),0,math.random(-wanderDistance,wanderDistance))
getPath(bodyRoot.Position,wanderPos)
end
if not goingDirect then
if closestHRP or wanderDistance > 0 then
for i = 1,closestHRP and 5 or #Waypoints-1 do -- Go to first 5 nodes if chasing players then re-route. If not chasing player then go to node before last
CurrentPoint = i
local currentGoal = Waypoints[CurrentPoint]
if CurrentPoint + 1 >= BlockedPoint then break end --Re-calculate
if currentGoal then
if not closestHRP then --If there wasnt a human to chase and now there is
if not returntoWanderWhenAttackDB and getClosestHRP() then
break
end
end
Humanoid:MoveTo(currentGoal.Position)
if currentGoal.Action == Enum.PathWaypointAction.Jump then
Humanoid.Jump = true
end
local start = tick() --Wait until close to goal or timeout (This seemed to work better than MoveToFinished)
repeat task.wait() until (bodyRoot.Position - currentGoal.Position).Magnitude <= 3 or tick()-start >= 2
if #debugTable > 0 then --Remove point from debug vis
debugTable[1]:Destroy()
table.remove(debugTable,1)
end
if Waypoints[CurrentPoint + 1] then --Start moving to next point while route re-calculates (This seemed to fix initial stutter as it moves to next point while calculating)
Humanoid:MoveTo(Waypoints[CurrentPoint + 1].Position)
end
end
end
end
end
end
I am attaching the place file if anyone would be so generous to take a look, provide feedback, and maybe solve the issue?
ai.rbxl (48.2 KB)
But at this point I think I can solve the issue. I moved a bunch of stuff around trying to fix the problem caused by network ownership.