Hello. I’m creating a game that requires Roblox’s pathfinding agent to simply jump to reach elevated areas. On paper, this sounds simple, but the agent is incapable of doing this ingame for some reason.
The video shows the agent trying and failing over and over again trying to jump onto the object. No, it is not because of the slope angle or collision, the agent will do this on every single platform regardless of shape or size. His behavior value is set to “Forward” during the entirely of the video and his target is above him on the elevated area you can see just out of frame.
Pretty much any time the AI is faced with jumping onto an elevated platform, it will do this. For some reason, it does not have trouble jumping over an obstacle, just onto one.
One band-aid fix I’ve tried employing is just removing the :wait() from it’s pathfinding script, which somewhat patched the issue but now they jump repeatedly and it doesn’t look very pretty.
Here’s the entirety of the pathfinding code. Please forgive the messiness.
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Dummy = script.Parent
local Humanoid: Humanoid = Dummy:WaitForChild("Humanoid")
local Root: BasePart = Dummy:WaitForChild("HumanoidRootPart")
local MaxDis = Dummy.NPC.MaxDistance
local behavior = Dummy.WalkBehavior
local nodes = game.Workspace.Nodes:GetChildren()
local MaterialCosts = {}
MaterialCosts.AIGoodNode = 0
MaterialCosts.AIBadNode = 50000
MaterialCosts.AIKillNode = math.huge
local Path = PathfindingService:CreatePath({
AgentHeight = 6,
AgentRadius = 6,
AgentCanJump = true,
AgentCanClimb = false,
Costs = MaterialCosts
})
local PathWaypoints = {}
local PathCurrent = 0
local PathLength = 0
-- Track progress
local lastPosition = Root.Position
local stuckTime = 0
local stuckThreshold = 5 -- seconds
local minMoveDistance = 6 -- studs
local function recomputePath(targetPos)
Path:ComputeAsync(Root.Position, targetPos)
if Path.Status == Enum.PathStatus.Success then
PathWaypoints = Path:GetWaypoints()
PathCurrent = 2
PathLength = #PathWaypoints
else
PathWaypoints = {}
PathCurrent = 0
PathLength = 0
end
end
task.spawn(function()
while task.wait(0.05) do
if Humanoid.Health <= 0 then return end
local PlayerCharacter = Dummy.target.Value
if not (PlayerCharacter and PlayerCharacter:FindFirstChild("HumanoidRootPart") and PlayerCharacter.Humanoid.Health > 0) then
continue
end
local distance = (Root.Position - PlayerCharacter.HumanoidRootPart.Position).Magnitude
if distance <= MaxDis.Value then
recomputePath(PlayerCharacter.HumanoidRootPart.Position)
end
end
end)
task.spawn(function()
while task.wait(0.01) do
if Humanoid.Health <= 0 then return end
local PlayerCharacter = Dummy.target.Value
if not (PlayerCharacter and PlayerCharacter:FindFirstChild("HumanoidRootPart") and PlayerCharacter.Humanoid.Health > 0) then
continue
end
local PathWaypointCurrent = PathWaypoints[PathCurrent]
if not PathWaypointCurrent then continue end
if behavior.Value == "Forward" then
Humanoid:MoveTo(PathWaypointCurrent.Position)
local requiresJump = PathWaypointCurrent.Action == Enum.PathWaypointAction.Jump
local jumpLoop = true
if requiresJump then
task.spawn(function()
while jumpLoop and Humanoid.Health > 0 do
Humanoid.Jump = true
task.wait(0.25)
end
end)
end
if (Root.Position - lastPosition).Magnitude > minMoveDistance then
lastPosition = Root.Position
stuckTime = 0
else
stuckTime += 0.1
end
if stuckTime >= stuckThreshold then
stuckTime = 0
recomputePath(PlayerCharacter.HumanoidRootPart.Position)
continue
end
Humanoid.MoveToFinished:Wait()
jumpLoop = false
PathCurrent += 1
elseif behavior.Value == "Backpedal" and Dummy.NPC.Backpedal.Value then
Retreat(PlayerCharacter)
elseif behavior.Value == "Still" then
if Dummy.NPC.Strafe.Value or math.random(1, 15) == 1 then
Strafe(PlayerCharacter)
end
end
end
end)
function Retreat(Target)
local result,pos = checkDirection(Dummy,-(Target.HumanoidRootPart.Position - Dummy.HumanoidRootPart.Position).Unit)
if result then
Dummy.Humanoid:MoveTo(pos)
return
end
result,pos = checkDirection(Dummy,Dummy.HumanoidRootPart.CFrame.RightVector)
if result then
Dummy.Humanoid:MoveTo(pos)
return
end
result,pos = checkDirection(Dummy,-Dummy.HumanoidRootPart.CFrame.RightVector)
if result then
Dummy.Humanoid:MoveTo(pos)
return
end
end
function Strafe(Target)
if Target then
local goalPos = Target.HumanoidRootPart.Position
local origin,dir = Dummy.HumanoidRootPart.Position,nil
local action = math.random(2)
if action == 1 then
dir = Dummy.HumanoidRootPart.CFrame.RightVector
elseif action == 2 then
dir = -Dummy.HumanoidRootPart.CFrame.RightVector
end
for _=1,10 do
if not raycast(origin,dir*2) and raycast(origin,Vector3.new(0,-7,0)) then
origin += dir*math.random(1.2,2)
else
origin += dir*-math.random(1.2,2)
break
end
end
local _,result = raycast(origin,(Target.HumanoidRootPart.Position - origin).Unit * 30)
if result then
if result.Instance:IsDescendantOf(Target.HumanoidRootPart.Parent) then
else
origin = Dummy.HumanoidRootPart.Position
end
origin = Dummy.HumanoidRootPart.Position
end
Dummy.Humanoid:MoveTo(origin)
Dummy.Humanoid.MoveToFinished:Wait()
end
end
function checkDirection(npc, dir)
if npc.target.Value then
local goalPos = npc.target.Value.HumanoidRootPart.Position
local origin = Dummy.HumanoidRootPart.Position
local success = false
for i=1, 5 do
if not raycast(origin,dir*1) and raycast(origin,Vector3.new(0,-7,0)) then
origin = origin + dir*1
else
origin = origin + dir*-1
break
end
if i == 5 then
success = true
end
end
return success,origin
end
end
function raycast(origin,dir)
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {Dummy, nodes}
local result = workspace:Raycast(origin,dir,raycastParams)
if result then
return true, result
else
return false
end
end
If anyone else has encountered this issue and has a solution, I would appreciate it.