I’m trying to make a pathfinding system, and so far, it seems to work all right when in patrol mode.
The problem is that is very easily breaks if it goes into a hallway that is kinda cramped (but still has enough space to easily go through) or if it has to climb something like a truss or a staircase.
Another problem is that when chasing a player this issue gets even worse, since when so it bumps into walls a lot, gets stuck in walls a lot, and can barely climb a truss or do any jumps without spinning around and breaking
I know roblox’s pathfinding is pretty clunky and hard to code for that reason, but any help is appreciated.
This is my code, it's pretty long and uses functions because I wanted to keep it as clean as possible.
local maxDist = 100
local ischasingplayer = false
local isDebugEnabled = true
local Pathfinding = game:GetService("PathfindingService")
local tw = game:GetService("TweenService")
script.Parent.HumanoidRootPart:SetNetworkOwner(nil)
function findTarget(thing)
for i,v in pairs(workspace:GetChildren()) do
if v:IsA"Model" then
if isDebugEnabled == true then
print("Model found")
end
for i_1,v_1 in pairs(game.Players:GetChildren()) do
if v.Name == v_1.Name then
if isDebugEnabled == true then
print("Turns out, model is player")
end
if (v.PrimaryPart.Position - script.Parent.PrimaryPart.Position).Magnitude <= maxDist then
if isDebugEnabled == true then
print("Dist was respected")
end
ischasingplayer = true
return v
else
ischasingplayer = false
end
end
end
end
end
end
function attack(path,wpoints)
if path then
if wpoints then
path.Changed:Connect(function(property: string)
wait()
return
end)
path.Blocked:Connect(function()
wait()
return
end)
if path.Status == Enum.PathStatus.Success then
for i_2, v_2 in pairs(wpoints) do
--if ischasingplayer == true then
if i_2 > 1 then
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
print("Moving To Player")
end
if v_2.Action == Enum.PathWaypointAction.Jump then
script.Parent.Humanoid.Jump = true
end
wait(.05)
else
if isDebugEnabled == true then
print("skipped")
end
end
--end
--script.Parent.Humanoid.MoveToFinished:Wait()
end
else
if isDebugEnabled == true then
print("path unsuccesful")
end
end
end
end
return
end
function patrol(path,wpoints)
if path then
if wpoints then
path.Changed:Connect(function(property: string)
return
end)
path.Blocked:Connect(function()
return
end)
if path.Status == Enum.PathStatus.Success then
for i_2, v_2 in pairs(wpoints) do
if ischasingplayer == false then
if i_2 > 1 then
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
print("Moving")
end
wait(.3)
else
if isDebugEnabled == true then
print("skipped")
end
end
end
end
--script.Parent.Humanoid.MoveToFinished:Wait()
else
if isDebugEnabled == true then
print("path unsuccesful")
end
end
end
end
end
function chase(v)
if v then
local path = Pathfinding:CreatePath(
{
AgentRadius = 2;
AgentHeight = 6;
AgentCanJump = true;
AgentCanClimb = true;
WaypointSpacing = 5;
Costs = {
Water = 100;
BrickWall = math.huge
}
}
)
if isDebugEnabled == true then
print("Path created")
end
if v:IsA("Model") then
path:ComputeAsync(script.Parent.PrimaryPart.Position, v.PrimaryPart.Position)
end
local wpoints = path:GetWaypoints()
return path,wpoints
else
local thing = findWayPoint()
local path = Pathfinding:CreatePath(
{
AgentRadius = 2;
AgentHeight = 6;
AgentCanJump = true;
AgentCanClimb = true;
WaypointSpacing = 5;
Costs = {
Water = 100;
BrickWall = math.huge
}
}
)
if isDebugEnabled == true then
print("Path created towards waypoint")
end
path:ComputeAsync(script.Parent.PrimaryPart.Position, thing.Position)
local wpoints = path:GetWaypoints()
return path,wpoints
--[[if isDebugEnabled == true then
print("no player yet")
end]]
end
end
function findWayPoint()
local fold = workspace.Waypoints:GetChildren()
local waypoint = fold[math.random(1,#fold)]
return waypoint
end
while wait() do
if isDebugEnabled == true then
print("Loop started")
end
local player = findTarget()
local path, wpoints = chase(player)
if ischasingplayer then
attack(path,wpoints)
else
patrol(path,wpoints)
end
end
I played around with the script and now it computes the paths almost perfectly when walking, but breaks the moment it has to jump or climb
The code I edited:
function attack(path,wpoints)
if path then
if wpoints then
path.Changed:Connect(function(property: string)
wait()
return
end)
path.Blocked:Connect(function()
wait()
return
end)
if path.Status == Enum.PathStatus.Success then
for i_2, v_2 in pairs(wpoints) do
--if ischasingplayer == true then
if i_2 > 1 then
if isDebugEnabled == true then
print("Moving To Player")
end
if v_2.Action == Enum.PathWaypointAction.Jump then
script.Parent.Humanoid.Jump = true
elseif v_2.Action == Enum.PathWaypointAction.Walk then
script.Parent.Humanoid:MoveTo(v_2.Position)
wait(.1)
return
end
wait(.75)
else
if isDebugEnabled == true then
print("skipped")
end
end
--end
--script.Parent.Humanoid.MoveToFinished:Wait()
end
else
if isDebugEnabled == true then
print("path unsuccesful")
end
end
end
end
return
end
Another thing I should note is that the patroling state and the chasing state are 2 different functions, and that for some reason it can’t jump no matter what, but when patrolling it can climb, unlike when chasing a player
The JumpPower has remained unchanged (50) and it can jump if I go to server side and change the jump value
And removing the return will prevent the rig from computing the pathfinding correctly, since it’ll go to the player’s position when it was detected and then it would only compute the player’s position after it reached the previous one
It was actually the return that was breaking, since it was resetting the function each time, so I made it check what instance of waypoint it was and return only if it exceeded a certain value
Final code:
local maxDist = 255 -- How close does the player have to be in order for the bot to detect
local ischasingplayer = false -- DO NOT CHANGE
local isDebugEnabled = true -- If true, the script will log every action in the Output tab (or dev console if not in studio)
local requiresLineOfSight = true -- UNUSED
local impVal = 4 -- Amount of waypoints before reset
local CanJump = true -- Should entity be able to jump (setting this to false and expecting your entity to jump will completly break the pathfinding)
local CanClimb = true -- Same as last one but applies with climbing instead (If set to true it will, unless with a pathfinding link, only go up trusses!)
local Width = 2 -- How wide should the entity be considered according to the pathfinding, since it isn't automaticly set
local Height = 6 -- Same as last one but applies with height
local WaypointSpacingValue = 5 -- How spaced out are the pathfinding's waypoints (Without these it can't compute a correct path)
local WaypointsFold = workspace.Waypoints -- Waypoints for the entity to go to when there isn't a player
local movecooldown = 1.6/script.Parent.Humanoid.WalkSpeed -- DO NOT CHANGE
local Pathfinding = game:GetService("PathfindingService")
script.Parent.HumanoidRootPart:SetNetworkOwner(nil)
function findTarget(thing)
for i,v in pairs(workspace:GetChildren()) do
if v:IsA"Model" then
if isDebugEnabled == true then
print("Model found")
end
for i_1,v_1 in pairs(game.Players:GetChildren()) do
if v.Name == v_1.Name then
if isDebugEnabled == true then
print("Turns out, model is player")
end
if (v.PrimaryPart.Position - script.Parent.PrimaryPart.Position).Magnitude <= maxDist then
if isDebugEnabled == true then
print("Dist was respected")
end
ischasingplayer = true
return v
else
ischasingplayer = false
end
end
end
end
end
end
function attack(path,wpoints)
if path then
if wpoints then
path.Changed:Connect(function()
wait(movecooldown)
return
end)
path.Blocked:Connect(function()
wait(movecooldown)
return
end)
if path.Status == Enum.PathStatus.Success then
for i_2, v_2 in pairs(wpoints) do
--if ischasingplayer == true then
if i_2 > 1 and i_2 <= impVal then
if isDebugEnabled == true then
print("Moving To Player")
end
--script.Parent.Humanoid:MoveTo(v_2.Position)
if v_2.Action == Enum.PathWaypointAction.Jump then
script.Parent.Humanoid.Jump = true
if isDebugEnabled == true then
warn("Succesfully jumped")
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
elseif v_2.Action == Enum.PathWaypointAction.Walk then
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
warn("FOLLOWS INTENDED PATH "..i_2)
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
continue
else
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
warn("ok what")
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
end
--wait(.75)
--wait(.1)
--return
elseif i_2 > impVal then
if isDebugEnabled == true then
print("Moving To Player")
end
--script.Parent.Humanoid:MoveTo(v_2.Position)
if v_2.Action == Enum.PathWaypointAction.Jump then
script.Parent.Humanoid.Jump = true
if isDebugEnabled == true then
warn("Succesfully jumped")
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
elseif v_2.Action == Enum.PathWaypointAction.Walk then
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
warn("FOLLOWS INTENDED PATH "..i_2)
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
break
else
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
warn("ok what")
end
wait(movecooldown*(1/(WaypointSpacingValue/5)))
end
--wait(.75)
--wait(.1)
--return
else
if isDebugEnabled == true then
print("skipped")
end
end
--end
--script.Parent.Humanoid.MoveToFinished:Wait()
end
else
if isDebugEnabled == true then
print("path unsuccesful")
end
end
end
end
end
function patrol(path,wpoints)
if path then
if wpoints then
path.Changed:Connect(function(property: string)
return
end)
path.Blocked:Connect(function()
return
end)
if path.Status == Enum.PathStatus.Success then
for i_2, v_2 in pairs(wpoints) do
if ischasingplayer == false then
if i_2 > 1 then
script.Parent.Humanoid:MoveTo(v_2.Position)
if isDebugEnabled == true then
print("Moving")
end
if v_2.Action == Enum.PathWaypointAction.Jump then
--script.Parent.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
script.Parent.Humanoid.Jump = true
end
--[[if ischasingplayer == true then
return
end]]
wait(movecooldown*(3/(WaypointSpacingValue/5)))
else
if isDebugEnabled == true then
print("skipped")
end
end
end
end
--script.Parent.Humanoid.MoveToFinished:Wait()
else
if isDebugEnabled == true then
print("path unsuccesful")
end
end
end
end
return
end
function chase(v)
if v then
local path = Pathfinding:CreatePath(
{
AgentRadius = Width;
AgentHeight = Height;
AgentCanJump = CanJump;
AgentCanClimb = CanClimb;
WaypointSpacing = WaypointSpacingValue;
Costs = {
Water = 100;
BrickWall = math.huge
}
}
)
if isDebugEnabled == true then
print("Path created")
end
if v:IsA("Model") then
path:ComputeAsync(script.Parent.PrimaryPart.Position, v.PrimaryPart.Position)
end
local wpoints = path:GetWaypoints()
return path,wpoints
else
local thing = findWayPoint()
local path = Pathfinding:CreatePath(
{
AgentRadius = Width;
AgentHeight = Height;
AgentCanJump = CanJump;
AgentCanClimb = CanClimb;
WaypointSpacing = WaypointSpacingValue;
Costs = {
Water = 100;
BrickWall = math.huge
}
}
)
if isDebugEnabled == true then
print("Path created towards waypoint")
end
path:ComputeAsync(script.Parent.PrimaryPart.Position, thing.Position)
local wpoints = path:GetWaypoints()
return path,wpoints
--[[if isDebugEnabled == true then
print("no player yet")
end]]
end
end
function findWayPoint()
local fold = WaypointsFold:GetChildren()
local waypoint = fold[math.random(1,#fold)]
return waypoint
end
while wait() do
movecooldown = 0.00625*script.Parent.Humanoid.WalkSpeed
if isDebugEnabled == true then
print("Loop started")
end
local player = findTarget()
local path, wpoints = chase(player)
if ischasingplayer then
attack(path,wpoints)
else
patrol(path,wpoints)
end
end