Pathfinding with large agents

I’m trying to make a pathfinding system using large agents.

The problem is the pathfinding system.

Using small agents it works fine. But as soon as i make the agent larger than normal, they either pathfind on top of ledges and get stuck, or they try and walk through walls.

Obviously, this is not desired.

I’m using the path finding script provided by roblox inside of a while loop, running every half a second, and I’ve adjusted the path params to match the agents size.

Is this ‘normal’ behaviour? Is the pathfinding system just flawd to mainly support regular sized agents?

I’ll supply code in a moment if neccessary, cause I’m in my phone, but I mean… it’s just calculating a path. What more is there to say?
I’ve spent forever trying to solve this by increasing wall size, lowering agent height. Increasing radius… it shouldnt be this ridiculous to figure out…

adjusting the wait time makes it a bit more smooth, as its not calculating a new path to follow so frequently… but it makes my boss seem ‘dumb’. He should just follow me. (and thats with an agent size of 16 (which is still glitchy), any larger and hes trying to do some pretty ridiculous things)

local PathfindingService = game:GetService("PathfindingService")
 
-- Variables for the boss, its humanoid, and destination
local boss = script.Parent.Parent;
local humanoid = boss:WaitForChild("Humanoid")
local myRoot = boss:WaitForChild("HumanoidRootPart")
local destination;

local bossHeight = math.ceil((0.5 * humanoid.RootPart.Size.Y) + humanoid.HipHeight);
local bossWidth = math.ceil(myRoot.Size.X * 2);
print (bossHeight, bossWidth)
local pathParams = {["AgentRadius"] = bossWidth, ["AgentHeight"] = bossHeight, ["AgentCanJump"] = true}

-- Create the path object
local path = PathfindingService:CreatePath(pathParams)
 
-- Variables to store waypoints table and boss's current waypoint
local waypoints
local currentWaypointIndex
 
local function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	end
end

local function followPath(destinationObject)
	-- Compute and check the path
	path:ComputeAsync(boss.LeftFoot.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints = {}
 
	if path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start boss walking
		waypoints = path:GetWaypoints()
		local f = game.Workspace:FindFirstChild("Waypoints");
		if f ~= nil then f:Destroy(); end
		f = Instance.new("Folder", game.Workspace);
		f.Name = "Waypoints"
		
		for _, waypoint in pairs(waypoints) do
			local part = Instance.new("Part")
			part.Shape = "Ball"
			part.Material = "Neon"
			part.Size = Vector3.new(0.6, 0.6, 0.6)
			part.Position = waypoint.Position
			part.Anchored = true
			part.CanCollide = false
			part.Parent = game.Workspace.Waypoints;
		end
		-- Move to first waypoint
		currentWaypointIndex = 3
		if #waypoints < currentWaypointIndex then
			currentWaypointIndex = 1
		end
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
		humanoid.MoveToFinished:Connect(onWaypointReached)
	else
		-- Error (path not found); stop humanoid
		--humanoid:MoveTo(boss.HumanoidRootPart.Position)
		humanoid.Jump = true;
		followPath(destinationObject);
	end
end
 
local function onPathBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex then
		-- Call function to re-compute the path
		followPath(destination)
	end
end

function isPlayer(target)
	local players = game.Players:GetChildren();
	for i, n in pairs(players)do
		if n.Name == target.Name then
			return n;
		end
	end
	return nil;
end

function GetDestination()--game.Workspace:WaitForChild("Paradigm_Dinosaurs");
	for i, player in pairs(workspace:GetChildren()) do
		if isPlayer(player) ~= nil then
				return player;
		end
	end
end

-- Connect 'Blocked' event to the 'onPathBlocked' function
path.Blocked:Connect(onPathBlocked)
 
-- Connect 'MoveToFinished' event to the 'onWaypointReached' function

while true do	
	boss.PrimaryPart:SetNetworkOwner(nil);	
	findPlayer = GetDestination()
	-- if I've found a player, and I can see them
	if findPlayer ~= nil then
		followPath(findPlayer)
	end
	wait(0.5)
end

I’m a big fan of writing your own pathfinder. It was one of the first projects that got me into computer science proper. It was tons of fun and I learned a lot. 10/10, would recommend.

I have a lot of pathfinder code and posted a lot of pathfinder related topics. Searching my posts for “pathfinder” should come up with some good results to help you.

Oh yeah, if you are wondering if the pathfinding system is flawed, you would be correct. Properly handling agents of different sizes requires calculating a new navigation mesh. From the behavior I assume the pathfinder is just shrinking the edges of polygons near obstacles (the Detour library Roblox uses can do this) but if the agent radius is larger than the polygons then the polygon can’t shrink that much. The navigation mesh needs to be altered, which can be an expensive operation.

2 Likes

K well I searched through your posts and couldn’t find anything helpful for my situation. So I did a bit more tinkering and scrapped the old code and came up with this:

This works for the most part, its still kinda glitchy cause the boss reaches his last way point sometimes before updating to the new path and stands for a second so it looks like hes unsure of what he wants to do for a split of a second.

The above problem isn’t nearly as bad as this though:
He seems to think hes short enough to go under a particular area in the map, but keeps banging his head on the platform Until I move out from under it… I dont think its an agent height issue, cause I adjusted it manually to 30, and again to 40 (exceeding the npcs height) and he still tries to go under the platform.
I am printing path status and it is showing success. Which it shouldn’t be cause he can’t get to me… furthermore, the print status prints every second, as it should if I am not under the platform. While under the platform, the path status only prints every 5 seconds, as though its not passing the return I’ve implemented to allow for the boss to walk to the player, rather than the players last position, which is due to the for loop (I tried to omit that in the previous code, but a for loop seems for convenient for handling waypoints)

Anyone have any ideas on how I can improve on this? I can program some pretty interesting things, but this pathfinding system is rediculous.

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

local bossHeight = math.ceil((0.5 * boss:WaitForChild("HumanoidRootPart").Size.Y) + humanoid.HipHeight);
local bossWidth = math.ceil(boss:WaitForChild("HumanoidRootPart").Size.X);
print (bossHeight, bossWidth);
local pathParams = {["AgentRadius"] = bossWidth, ["AgentHeight"] = bossHeight, ["AgentCanJump"] = true};

local updatePath = false;

function isPlayer(target)
	local players = game.Players:GetChildren();
	for i, n in pairs(players)do
		if n.Name == target.Name then
			return n;
		end
	end
	return nil;
end

function GetDestination()--game.Workspace:WaitForChild("Paradigm_Dinosaurs");
	for i, player in pairs(workspace:GetChildren()) do
		if isPlayer(player) ~= nil then
			return player;
		end
	end
end

function move()
		
	local goal = GetDestination();
	if goal == nil then return; end
			
	if updatePath == true then
		updatePath = false;
		move();
		return;
	end
			
	local bossFeetMidPoint = (boss:WaitForChild("LeftFoot").Position + boss:WaitForChild("RightFoot").Position)/2;
	local playerFeetMidPoint = (goal:WaitForChild("LeftFoot").Position + goal:WaitForChild("RightFoot").Position)/2;
	local path = game:GetService("PathfindingService"):CreatePath(pathParams);
	path:ComputeAsync(bossFeetMidPoint, playerFeetMidPoint);
	local waypoints = path:GetWaypoints();
	
	print (path.Status)
	
	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in pairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid.Jump = true;
			end
			humanoid:MoveTo(waypoint.Position);
			local timeOut = humanoid.MoveToFinished:Wait(1)
			-- path was too long, jump and try again
            if not timeOut then
                humanoid.Jump = true
                move()
                break
            end
		end
		move();
	else
		humanoid.Jump = true;
		move();
	end
end

while wait(1) do
	boss.HumanoidRootPart:SetNetworkOwner(nil);
	updatePath = true;
	move();
end

EDIT:
w/e. Im just going to do some more programming to it, and force him to stop when theres an obstacle at his face.

The reason it doesn’t seem to apply to you is that you are still trying to use the built-in pathfinder. A lot of the stuff I’ve written is about custom pathfinding. A major issue with the built-in pathfinder is that it isn’t customizable and is a black box. Sometimes you can only guess why something is failing.

If you are intent on sticking with the built-in pathfinder, my advice would be to play with it and try to identify what causes the bugs and then try to find a way to avoid them. Many developers roll their own pathfinders for specific use cases when they get fed up with the issues in the built-in one so I doubt people have put such time to figure out the particulars of these bugs and doubt even more that there is a solution to them. You’ll likely have to do that work yourself. :frowning:

1 Like

I do appreciate your feedback.
The thing is, I am trying not to invest a whole lot of time into this one problem. I believe the issue is that I’m pathing from mid feet pos to mid feet pos, and the path is still pointing to the player regardless of headspace.

that said, I feel like if I just compensate for the head, and perhaps have him duck under the platform he would obviously fit and go after the player.

Beyond that, I have no idea where to start with pathfinding, ironically (because you seem to love it), it has never been something I’ve had interest in solving, and thus have avoided it until now. You’d think that something so menial would have already been compensated for.

1 Like