Path.Blocked firing without any real obstacles

Attached below are a few screenshots containing the navigation mesh of a maze as well as where path.Blocked is firing, (20x20 maze from this post)

The white block represents where Path.Blocked is firing, it seems to be firing as the AI needs to jump down to that position. This causes unintended behavior by my NPC. One possible workaround I have found is I can instead include a sort of ramp so it does not have to jump down (I applied this solution so the NPC could go up stairs as blocked would fire similarly for stairs).

Similar path.Blocked misfiring behavior happens when the AI is required to jump over a simple blockaid, but I have no possible workarounds to this that do not involve placing ramps.

After a bit of testing also, I have found that the ramp has to be gently sloping in order for this fix to work properly. Steep ramps will not work properly.

These are all solutions that can be applied through map design, but are there any alternative fixes you have applied or that I could experiment with?

Fall example

Fall example fix

Stairs example

Potential fix for the stairs

Ramp Example

Code for the NPC

local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath({
	AgentRadius = 3,
	AgentHeight = 6,
	AgentCanJump = true,
	WaypointSpacing = 5,
	Costs = {
		Water = 20
	}
})

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")

local TEST_DESTINATION = workspace.Destination 

local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local unblockedConnection

local function followPath(destination)
	-- Compute the path
	local success, errorMessage = pcall(function()
		path:ComputeAsync(character.PrimaryPart.Position, destination)
	end)
	
	humanoid:MoveTo(humanoid.RootPart.Position)
	
	if success and path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints
		waypoints = path:GetWaypoints()
		if blockedConnection then
			blockedConnection:Disconnect()
			blockedConnection = nil
		end
		if reachedConnection then
			reachedConnection:Disconnect()
			reachedConnection = nil
		end
		if unblockedConnection then
			unblockedConnection:Disconnect()
			unblockedConnection = nil
		end
		humanoid:MoveTo(humanoid.RootPart.Position)

		-- Detect if path becomes blocked
		blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			-- Check if the obstacle is further down the path
			--[[print("BLOCKED")
			local PartInstance = Instance.new("Part")
			PartInstance.Size = Vector3.new(1,1,1)
			PartInstance.Material = Enum.Material.Neon
			PartInstance.Color = Color3.fromRGB(255,255,255)
			PartInstance.Anchored = true
			PartInstance.Position = waypoints[blockedWaypointIndex].Position
			PartInstance.CanCollide = false
			PartInstance.Name = "BLOCKEDPOS"
			PartInstance.Parent = workspace]]
			if blockedWaypointIndex >= nextWaypointIndex then
				--print("Blocked ahead of us")
				-- Stop detecting path blockage until path is re-computed
				reachedConnection:Disconnect()
				reachedConnection = nil
				blockedConnection:Disconnect()
				humanoid:MoveTo(humanoid.RootPart.Position)
				-- Call function to re-compute new path
				followPath(destination)
			end
		end)
		
		--[[unblockedConnection = path.Unblocked:Connect(function(unblockedWaypointIndex)
			print("path unblocked at ".. unblockedWaypointIndex)
		end)]]
		-- Detect when movement to next waypoint is complete
		if not reachedConnection then
			reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
				if reached and nextWaypointIndex < #waypoints then
					-- Increase waypoint index and move to next waypoints					
					nextWaypointIndex += 1
					if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump and humanoid:GetState() ~= Enum.HumanoidStateType.Jumping then
						humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					end
					humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
				else
					reachedConnection:Disconnect()
					reachedConnection = nil
					humanoid:MoveTo(humanoid.RootPart.Position)
					if blockedConnection then	
						blockedConnection:Disconnect()
					end
					if unblockedConnection then
						unblockedConnection:Disconnect()
					end
				end
			end)
		end

		-- Initially move to second waypoint (first waypoint is path start; skip it)
		nextWaypointIndex = 2
		humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
	else
		warn("Path not computed!", errorMessage)
	end
end

script.Parent.Humanoid.RootPart:SetNetworkOwner(nil)

while true do
	task.wait(4)
	followPath(TEST_DESTINATION.Position)
end

Character also does tend to get stuck on the blocked waypoint
image

1 Like

try changing the AgentParamaters in the CreatePath function.

next time include your code so its easier to troubleshoot the issue.

just adjusted agent params, no difference found, also added the code.

Try reducing/increasing waypoint spacing.

This made no difference, increasing and decreasing still caused .Blocked to fire on the same blockaid, I am thinking about potentially making it so the code on .Blocked only fires if the blockedwaypoint is within 10 waypoints from my character, but this is only a bandaid solution.

I’m starting to think it’s something completely on Roblox’s end. With “Show Navigation Mesh” enabled, while the game is running the navigation mesh seems to generate in a large rectangular shape going from the NPC to it’s destination. Just outside of this rectangle is the corridor it needs to go through to get to it’s destination. After moving the NPC towards said corridor in a straight line, it finds a path.

But here’s the thing, trying to compute a path BEFORE the game is running leads to it not having any of these problems. The navigation mesh is generated for the entire map, so it can always find a path if it’s possible. This is most definitely some sort of bug.

That’s what I was thinking as well.

Also if possible could you attach a screenshot because I am having trouble understanding what you mean.

If needed, I can attach my place file.

Before running game:

While game is running and NPC is trying to pathfind:

As you can see, the yellow dots automatically shown when “Show Navigation Mesh” is enabled to indicate path waypoints are generated in the first picture, where I’ve used the command bar to generate a path.

In the second picture, however, when the NPC’s script tried to generate a path while the game is running, it fails, so no yellow waypoints can be seen.

How’s this relate to when waypoint is blocked though?

I’m not entirely sure how your NPC is set up, but I’m guessing you generate a path multiple times and when it fails .Blocked fires.

The problem is that path.Blocked is firing without any obstacles, like seen in the screenshots shown for the ramp example and fall example.

The path is only being generated once, and the connections for .Blocked and MoveToFinshed are only being made once

1 Like

Seems to be more of a problem with .Blocked. I’m managed to get it to fire, but nothing happens. It just fires, and the waypoints that’s “blocked” is just out in the open. The NPC continues along, it doesn’t get stuck. Here’s the place file:

blocked fires when not blocked.rbxl (95.9 KB)

The path itself is generated, and not blocked when it’s generated, and nothing in the world changes, so it’s pretty weird .Blocked fires and says some waypoint right next to the destination is blocked.

Also, this is because the code stops running because you try to make the NPC stop moving with humanoid:MoveTo(humanoid.RootPart.Position).

This looks like a one-path type of maze, as opposed to a multipath(no dead ends). I gave up trying to use the pathfinding service for this set up last summer and instead used depth-first search algorithm which works perfectly.

On the other hand my maze is completely flat.

Removed the Humanoid:MoveTo(RootPart.Position) and character still gets stuck in the corner.

image

Will probably add something to make it move in a direction opposite of the direction it is facing to unstuck

Decided to adopt a solution by not using Path.Blocked to detect if the path has been blocked since it was not working properly.

Instead of using path.Blocked, I’ve decided to detect if the path failed by detecting if the Humanoid successfully reached the next waypoint using Humanoid.MoveToFinished.

Unfortunately, this is not as good for detecting if waypoints further up have been blocked, a potential solution to this would be raycasting from Blocked waypoint position to detect any “problematic” instances, as path.Blocked does not return the instance that is blocking the path but only the waypoint.

Example function to detect if pathwaypoint is blocked:

local RaycastParas = --Your raycast params 

function DetectWaypointBlocked(WaypointPosition) -- ran using DetectWaypointBlocked(waypoints[blockedwaypointindex].Position)
	local DetectionRay = workspace:Raycast(WaypointPosition, -CFrame.new(WaypointPosition).UpVector * 10, RaycastParas)
	if DetectionRay then
       if DetectionRay.Instance then -- insert your methods to detect whether this is a valid blockaid
           if DetectionRay.Instance.Size.Y > 5 then 
    		   return true
           end
       end
	end
	return false
end

Below is the finalized code.

local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath({
	--AgentRadius = 3,
	--AgentHeight = 6,
	AgentCanJump = true,
	WaypointSpacing = 5,
	Costs = {
		Water = 20
	}
})

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")

local TEST_DESTINATION = workspace.Destination 

local LastJumpWaypoint = nil

local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection

--[[local RaycastParas = RaycastParams.new()
RaycastParas.FilterDescendantsInstances = {character}
RaycastParas.FilterType = Enum.RaycastFilterType.Blacklist

function DetectRootpartBlocked()
	local DetectionRay = workspace:Raycast(humanoid.RootPart.Position, humanoid.RootPart.CFrame.LookVector * 10, RaycastParas)
	if DetectionRay then
		if DetectionRay.Position > humanoid.RootPart.Position + 2 then
			return true
		end
	end
	return false
end
]]
local function followPath(destination)
	-- Compute the path
	local success, errorMessage = pcall(function()
		path:ComputeAsync(character.PrimaryPart.Position, destination)
	end)
	
	--humanoid:MoveTo(humanoid.RootPart.Position)
	if success and path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints
		waypoints = path:GetWaypoints()
		if blockedConnection then
			blockedConnection:Disconnect()
			blockedConnection = nil
		end
		if reachedConnection then
			reachedConnection:Disconnect()
			reachedConnection = nil
		end
		
		-- Detect if path becomes blocked
		--[[blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			-- Check if the obstacle is further down the path
			print("BLOCKED AT" .. tostring(waypoints[nextWaypointIndex].Position))	
			local PartInstance = Instance.new("Part")
			PartInstance.Size = Vector3.new(1,1,1)
			PartInstance.Material = Enum.Material.Neon
			PartInstance.Color = Color3.fromRGB(255,255,255)
			PartInstance.Anchored = true
			PartInstance.Position = waypoints[blockedWaypointIndex].Position
			PartInstance.CanCollide = false
			PartInstance.Name = "BLOCKEDPOS"
			PartInstance.Parent = workspace
			game.Debris:AddItem(PartInstance, 1)
			if blockedWaypointIndex >= nextWaypointIndex  and blockedWaypointIndex - nextWaypointIndex < 10 then
				--humanoid:Move(Vector3.new(math.random(-1,1),0,math.random(-1,1)))
				-- Stop detecting path blockage until path is re-computed
				reachedConnection:Disconnect()
				reachedConnection = nil
				blockedConnection:Disconnect()
				-- Call function to re-compute new path
				followPath(destination)
			end
		end)
		]]
		
		-- Detect when movement to next waypoint is complete
		if not reachedConnection then
			reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
				if reached and nextWaypointIndex < #waypoints then
					-- Increase waypoint index and move to next waypoints					
					nextWaypointIndex += 1
					if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump and humanoid:GetState() ~= Enum.HumanoidStateType.Jumping and LastJumpWaypoint ~= waypoints[nextWaypointIndex].Position then
						if LastJumpWaypoint == nil or (LastJumpWaypoint - waypoints[nextWaypointIndex].Position).Magnitude > 5 then
							LastJumpWaypoint = waypoints[nextWaypointIndex].Position
							print("Jumping at " .. tostring(LastJumpWaypoint))
							humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
						end						
					end
					humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
				else
					reachedConnection:Disconnect()
					reachedConnection = nil
					humanoid:MoveTo(humanoid.RootPart.Position)
					if blockedConnection then	
						blockedConnection:Disconnect()
					end
					if nextWaypointIndex < #waypoints then
						followPath(destination)
					end
				end
			end)
		end
		-- Initially move to second waypoint (first waypoint is path start; skip it)
		nextWaypointIndex = 2
		humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
	else
		warn("Path not computed!", errorMessage)
	end
end

script.Parent.Humanoid.RootPart:SetNetworkOwner(nil)

while true do
	task.wait(4)
	followPath(TEST_DESTINATION.Position)
end
5 Likes