So I’m working on a pathfinding AI that has 3 simple stages.
-
It attempts to Raycast to every player in order from closest to furthest. If it doesn’t find a player, it will choose a random waypoint (Part in a folder in the workspace) to pathfind to. It will loop this indefinitely until it finds a player. [Roaming]
-
If a player is found, it will immediately stop the Roaming and will move in the player’s direction. [Chasing]
-
If a player has just been chased, but the AI loses sight of them (failed Raycast), it will pathfind to the last location of the player. [Following]
The issue I’m having is when the AI enters the ‘Following’ stage, a path is made (indicated by the drawn out parts in the video) but the AI doesn’t seem to be following the waypoints. I am using coroutines to close the old Move() function that was active during ‘Roaming’ (Shown in the code) so maybe there’s an issue with that? I’ve been working on this for 2 days now and can’t seem to wrap my head around it. Any help would be appreciated.
Here is a screenshot of the Output. It shows the waypoint positions from the pathfinding printed out right after the AI enters the ‘Following’ mode. As you can see, the vectors are drastically different, and not consistent with the distance you’d get between 2 Waypoints.
Here is my code. It’s quite long, but the coroutines that call the Pathfind() function are in the Roam() function, and the while loop at the bottom.
-- {{Services}} --
local PlayerService = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
-- {{Settings}} --
local walkSpeed = 12
local runSpeed = 14
local rangeShort = 40
local rangeFar = 70
local pathfindParams = {
["AgentHeight"] = 7,
["AgentRadius"] = 6,
["AgentCanJump"] = false,
}
-- {{Variables}} --
local AI = "Idle"
local Cooldown = false
local CanSeePlayer = false
local Speed = walkSpeed
local Range = rangeShort
local PlayerBuffer
-- {{Objects}} --
local model = script.Parent.Parent
local humanoid = model:WaitForChild("Humanoid")
local humanoidRootPart = model:WaitForChild("HumanoidRootPart")
humanoidRootPart:SetNetworkOwner(nil)
local coroF
local coroR
-----------------------------------------------------------------------------------------------
function Delete()
for i, part in pairs(workspace.temp:GetChildren()) do
part:Destroy()
end
end
function Move(path)
if path.Status ~= Enum.PathStatus.Success then return end
local waypoints = path:GetWaypoints()
for i, waypoint in pairs(waypoints) do
local part = Instance.new("Part")
part.Name = i
part.Shape = Enum.PartType.Ball
part.Position = waypoint.Position
part.Material = Enum.Material.Neon
part.Size = Vector3.new(0.3,0.3,0.3)
part.Anchored = true
part.CanCollide = false
part.BrickColor = BrickColor.new("Neon green")
part.Parent = workspace.temp
end
for i, waypoint in pairs(waypoints) do
if i > 1 then -- Start on second waypoint
print(waypoint.Position)
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
Delete()
AI = "Idle"
Cooldown = false
end
-----------------------------------------------------------------------------------------------
function Pathfind(target)
humanoid:Move(Vector3.new())
if Cooldown == false then
Cooldown = true
local origin = humanoidRootPart.Position
local path = PathfindingService:CreatePath(pathfindParams)
path:ComputeAsync(origin, target)
Move(path)
end
end
-----------------------------------------------------------------------------------------------
function GetPlayersInOrder()
local players = PlayerService:GetPlayers()
local dT = {}
local pT = {}
for _, player in pairs(players) do
local charcter = player.Character or player.CharacterAdded:Wait()
if charcter then
local hrp = charcter:WaitForChild("HumanoidRootPart")
local origin = humanoidRootPart.Position
local target = hrp.Position
local distance = (target - origin).Magnitude
pT[distance] = player
table.insert(dT, distance)
end
end
table.sort(dT, function(a,b)
return a < b
end)
return dT, pT
end
-----------------------------------------------------------------------------------------------
function FindPlayer()
local dT, pT = GetPlayersInOrder()
for _, distance in pairs(dT) do
local player = pT[distance]
local character = player.Character or player.CharacterAdded:Wait()
if character then
if distance <= Range then
local RayParams = RaycastParams.new()
RayParams.FilterDescendantsInstances = {model}
RayParams.FilterType = Enum.RaycastFilterType.Exclude
local hrp = character:WaitForChild("HumanoidRootPart")
local origin = humanoidRootPart.Position
local target = hrp.Position
local direction = (target - origin).Unit
local RaycastResult = workspace:Raycast(origin, direction * Range, RayParams)
if RaycastResult then
if RaycastResult.Instance:IsDescendantOf(character) then
CanSeePlayer = true
return player
end
end
end
end
end
CanSeePlayer = false
end
-----------------------------------------------------------------------------------------------
function MoveToPlayer(player)
local character = player.Character or player.CharacterAdded:Wait()
if character then
local hrp = character:WaitForChild("HumanoidRootPart")
local origin = humanoidRootPart.Position
local target = hrp.Position
local direction = (target - origin).Unit
humanoid:Move(direction)
AI = "Chase"
if coroR then
coroutine.close(coroR)
Delete()
end
end
end
-----------------------------------------------------------------------------------------------
function Roam()
local waypoints = game.Workspace:WaitForChild("LoopPoints"):GetChildren()
local randomPoint = waypoints[math.random(1, #waypoints)]
coroR = coroutine.create(Pathfind)
coroutine.resume(coroR, randomPoint.Position)
end
-----------------------------------------------------------------------------------------------
while true do
local player = FindPlayer()
if player then
if PlayerBuffer ~= player and coroF then -- If the player has changed
coroutine.close(coroF)
Delete()
end
PlayerBuffer = player
MoveToPlayer(player)
else
if AI == "Idle" then
Roam()
end
end
--------------------------------------------------------------------------
if PlayerBuffer and player == nil then -- Player has hid and no new player
local character = PlayerBuffer.Character or PlayerBuffer.CharacterAdded:Wait()
if character then
Cooldown = false
AI = "Following"
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
local target = humanoidRootPart.Position
PlayerBuffer = nil
if coroR then
coroutine.close(coroR)
Delete()
end
coroF = coroutine.create(Pathfind)
coroutine.resume(coroF, target)
end
end
print(AI)
wait()
end