Hi, I have recently came across SimplePath, although its nice and works great, I am stepping into some problems.
Here I have my main ai module:
local Pathfinding = require(script.Pathfinding)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Ai = {}
Ai.__index = Ai
local ACTIVATION_RANGE = 150
local PATH_RECALC_INTERVAL = 0.5
local TARGET_MOVE_THRESHOLD = 5
function Ai.new(Rig)
local self = setmetatable({}, Ai)
self.Rig = Rig
self.Active = false
self.Target = nil
self.LastPathTime = 0
self.LastTargetPos = nil
self.PathFinding = Pathfinding.new(self.Rig, {AgentCanClimb = true})
return self
end
function Ai:Function()
self.Active = true
RunService.Heartbeat:Connect(function(dt)
if not self.Active then return end
self:Tick()
end)
self.Rig:FindFirstChildOfClass("Humanoid").Died:Once(function()
self:Stop()
end)
end
function Ai:Stop()
self.Active = false
self.PathFinding:Stop()
self.Target = nil
end
function Ai:Tick()
if self.Active == false then return end
-- Find nearest player in range
self.Target = self:SearchForTargets()
if self.Target then
self:Chase()
else
if self.PathFinding.Status == "Idle" then
self:Patrol()
end
end
end
function Ai:Chase()
if not self.Target or not self.Target:FindFirstChild("HumanoidRootPart") then return end
local now = os.clock()
local targetPos = self.Target.HumanoidRootPart.Position
local moved = (self.LastTargetPos and (targetPos - self.LastTargetPos).Magnitude > TARGET_MOVE_THRESHOLD)
if (now - self.LastPathTime) >= PATH_RECALC_INTERVAL or moved then
self.PathFinding:Run(self.Target.HumanoidRootPart)
if self.PathFinding._path.Status == Enum.PathStatus.NoPath then
self.PathFinding:Stop()
end
self.LastPathTime = now
self.LastTargetPos = targetPos
end
end
function Ai:Patrol()
if self.Active == false then return end
local RandomPos = self.Rig.HumanoidRootPart.Position + Vector3.new(
math.random(-50, 50),
0,
math.random(-50, 50)
)
self.PathFinding:Run(RandomPos)
self.PathFinding.WaypointReached:Once(function()
local newTarget = self:SearchForTargets()
if newTarget then
self.Target = newTarget
self.PathFinding:Stop()
self:Chase()
end
end)
end
function Ai:SearchForTargets()
if self.Active == false then return end
if not self.Rig.PrimaryPart then return nil end
local nearest, dist = nil, ACTIVATION_RANGE
for _, player in ipairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local root = player.Character.HumanoidRootPart
local mag = (root.Position - self.Rig.PrimaryPart.Position).Magnitude
if mag < dist then
nearest, dist = player.Character, mag
end
end
end
return nearest
end
return Ai
When a player is at an unreachable spot, for example on a building with no way for ai to get to it, the ai freezes in place, and when the player gets down, sometimes the ai does not unfreeze and stays stuck, no errors whatsoever, and sometimes it does unfreeze but it just moves randomly, not chasing any players. I have tried alot of solutions, it just became unbearable and I need any help possible!
The issue is that pathfinding failures aren’t being handled. When SimplePath can’t find a route, the AI should cancel the chase and let the target go.
It was freezing because it got stuck in a loop where it kept finding the target and failing really fast. To break that loop and give the AI some time to do the patrol action, we can make it ignore the target for a while after it fails to reach it. It’s not the most elegant solution, but maybe it gives you an idea of how to do it better.
local Pathfinding = require(script.Pathfinding)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Ai = {}
Ai.__index = Ai
local ACTIVATION_RANGE = 150
local PATH_RECALC_INTERVAL = 0.5
local TARGET_MOVE_THRESHOLD = 5
local UNREACHABLE_COOLDOWN = 3
function Ai.new(Rig)
local self = setmetatable({}, Ai)
self.Rig = Rig
self.Active = false
self.Target = nil
self.LastPathTime = 0
self.LastTargetPos = nil
self.UnreachableTargets = {}
self.PathFinding = Pathfinding.new(self.Rig, {AgentCanClimb = true})
self.PathFinding.Error:Connect(function(errorType)
-- error due to a failed calculation and we have a target
if (errorType == Pathfinding.ErrorType.ComputationError or errorType == Pathfinding.ErrorType.TargetUnreachable) and self.Target then
self.UnreachableTargets[self.Target] = os.clock()
-- force the patrol
if self.PathFinding.Status ~= Pathfinding.StatusType.Idle then
self.PathFinding:Stop()
end
self.Target = nil
end
end)
return self
end
function Ai:Function()
self.Active = true
RunService.Heartbeat:Connect(function(dt)
if not self.Active then return end
self:Tick()
end)
self.Rig:FindFirstChildOfClass("Humanoid").Died:Once(function()
self:Stop()
end)
end
function Ai:Stop()
self.Active = false
if self.PathFinding and self.PathFinding.Status ~= Pathfinding.StatusType.Idle then
self.PathFinding:Stop()
end
self.Target = nil
end
function Ai:Tick()
if not self.Active then return end
-- we only look for a new goal if we don't have one already
if not self.Target then
self.Target = self:SearchForTargets()
end
if self.Target then
self:Chase()
else
if self.PathFinding.Status == "Idle" then
self:Patrol()
end
end
end
function Ai:Chase()
if not self.Target or not self.Target:FindFirstChild("HumanoidRootPart") then
self.Target = nil
return
end
local now = os.clock()
local targetPos = self.Target.HumanoidRootPart.Position
local moved = (self.LastTargetPos and (targetPos - self.LastTargetPos).Magnitude > TARGET_MOVE_THRESHOLD)
if (now - self.LastPathTime) >= PATH_RECALC_INTERVAL or moved then
self.PathFinding:Run(self.Target.HumanoidRootPart)
self.LastPathTime = now
self.LastTargetPos = targetPos
end
end
function Ai:Patrol()
if not self.Active then return end
local rootPart = self.Rig:FindFirstChild("HumanoidRootPart")
if not rootPart then return end
local randomPos = rootPart.Position + Vector3.new(
math.random(-50, 50),
0,
math.random(-50, 50)
)
self.PathFinding:Run(randomPos)
end
function Ai:SearchForTargets()
if not self.Active then return nil end
if not self.Rig.PrimaryPart then return nil end
local nearest, dist = nil, ACTIVATION_RANGE
--- clearing the blacklist of expired entries
for target, markedTime in pairs(self.UnreachableTargets) do
if os.clock() - markedTime > UNREACHABLE_COOLDOWN then
self.UnreachableTargets[target] = nil
end
end
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
if not self.UnreachableTargets[character] then
local root = character.HumanoidRootPart
local mag = (root.Position - self.Rig.PrimaryPart.Position).Magnitude
if mag < dist then
nearest, dist = character, mag
end
end
end
end
return nearest
end
return Ai
Thank you so much for your response, I had a similar idea but the fact that I would have to exclude the player for a while which made me question it, not really sure why, anyways., I will test it out and let you know!