Jittery pathfinding with PathfindingService function

Howdy

I’m working on a pathfinding module that uses Roblox’s PathfindingService API. When the NPC detects an obstacle, I have it compute a path and follow it. This does work to some extent, but the NPC becomes very jittery. I have narrowed down the problem down to a ComputePath() function that the module has, and the way that I’m calling the function.

The GetSight() function just draws a ray from the NPC to the target to check if there is a direct path that can be more easily followed with the MoveTo() humanoid function.

function EntityController:ComputePath(ENTITYROOT,ENTITYHUM,TARGET)
	
	local PATH = PATHSERVICE:CreatePath()
	PATH:ComputeAsync(ENTITYROOT.Position,TARGET.Position)
	local WAYPOINTS = PATH:GetWaypoints()
	
	if PATH.Status == Enum.PathStatus.Success then
		
		for i,POINT in ipairs(WAYPOINTS) do
			if POINT.Action == Enum.PathWaypointAction.Jump then
				ENTITYHUM.Jump = true
			end
			ENTITYHUM:MoveTo(POINT.Position)
			
			local SUCCESS = ENTITYHUM.MoveToFinished:wait()
			if not SUCCESS or GetSight(ENTITYROOT,TARGET) then break end
			
			if i % 3 == 0 then -- for every 3rd waypoint, check again for new target
				if FindTarget(ENTITYROOT) ~= TARGET then
					break
				end
			end
		end
		
	end
end

Here’s a gif of the jittery movement. As you can see, it can follow an unobstructed path, but will become jittery when it tries to compute and follow a path using PathfindingService.

1 Like

Looks like it’s probably related to your GetSight function here. I’m guessing that’s returning a boolean which is false if the NPC cannot see its target. This causes the path to be reassessed, which is unnecessary. In my experience with the path finding service it helps to ignore the first point or two in a path since they tend to be so close to the NPC and tend to cause this back and forth motion you’re seeing. Just a hunch though.

It’s not safe to draw a ray from your NPC toward its target and assume the path is clear. Unless the NPC and target are roughly the same height, at the same position on the Y axis and are both very small there could be many objects between the two points that won’t actually block the ray. You would probably need to adapt a Region3 to do this instead, but even then it wouldn’t be totally accurate. If you’re sending out multiple rays then this method might work? Still, I think there are enough edge cases, like huge gaps between the NPC and target that would be an issue. Of course if that doesn’t exist in your game then it isn’t.

Additionally there’s not too much reason to reinitialize the Path object like is being done in the first line of the function here local PATH = PATHSERVICE:CreatePath() really only needs to happen once.

2 Likes

After some testing I was able to streamline my code and just remove the whole GetSight() bit altogether. Unfortunately, after a little while, the NPC’s movement goes from smooth to jittery. This is what it looks like after a few seconds of pathfinding. Also, skipping the first waypoint works wonderfully, so thank you for that.

	PATH:ComputeAsync(ENTITYROOT.Position,TARGET.Position)
	local WAYPOINTS = PATH:GetWaypoints()
	
	if PATH.Status == Enum.PathStatus.Success then
		
		for i=2,#WAYPOINTS do
			local POINT = WAYPOINTS[i]
			ENTITYHUM:MoveTo(POINT.Position)
			--[[ visualize waypoints
			local part = Instance.new("Part")
			part.Anchored = true
			part.Size = Vector3.new(1,1,1)
			part.CanCollide = false
			part.Position = POINT.Position
			part.Parent = workspace
			]]--
			if POINT.Action == Enum.PathWaypointAction.Jump then
				ENTITYHUM.Jump = true
			end
			
			local REACHED = ENTITYHUM.MoveToFinished:wait()
			
			if not REACHED then
				print('failed')
				break
			end
		end
	end
1 Like

Just another theory here, but could you try setting the zombie’s NetworkOwner to whoever they’re chasing?

1 Like

I know this is a bit of a necro, but I’m having the same problem with the pathfinding behavior taking long pauses inbetween it’s movetowaypoint system.

I’ve tried other methods of moving on waypoints but I can’t seem to get rid of that factor.

			local function moveToNextWaypoint()
			print("Waypoint step")
			if waypointIndex > #waypoints or yield then
				--pathBlockedEvent:Disconnect()
			else
				MobAsset.Humanoid:MoveTo(waypoints[waypointIndex].Position)
				waypointIndex = waypointIndex + 1
				MobAsset.Humanoid.MoveToFinished:Wait() -- yields too long
				moveToNextWaypoint()
			end
		end

I have older code from one of my older projects that has the same issue as well.

while wait() do
local head = findTarget()

if head then
	aiPart = script.Parent.HumanoidRootPart.Position
	local hit = rayPathfind(head)
	--local ray = Ray.new(aiPart, (head.Position - aiPart).Unit * 200)
	--local otherInfected = findOtherInfected()
	--local hit,position = workspace:FindPartOnRayWithIgnoreList(ray,otherInfected)
	if hit then
		print(hit)
		if hit:IsDescendantOf(head.Parent) then
			mob:MoveTo(head.Position)
			wait()
		else
			local path = pathServ:FindPathAsync(aiPart,head.Position)
		if path.Status == Enum.PathStatus.Success then
			points = path:GetWaypoints()
			--[[for i,v in ipairs(points) do
				local draw = Instance.new("Part",workspace)
				draw.Anchored = true
				draw.CanCollide = false
				draw.CFrame = CFrame.new(v.Position)
				draw.Size = Vector3.new(10,10,10)
			end]]
			for i,v in ipairs(points) do
				mob:MoveTo(v.Position)
				mob.MoveToFinished:wait()
				if v.Action == Enum.PathWaypointAction.Jump then
					mob.Jump = true -- I don't want the AI to jump, ever.
				end
				local hit2 = rayPathfind(head)
				if hit2 and hit2:IsDescendantOf(head.Parent) then
				break
				elseif (points[#points].Position - head.Position).magnitude > 15 then
					break
				end
			end
		else
			mob:MoveTo(head.Position) -- Idle animation? 
		end
		end
	end
end

end

2 Likes

I had the same issue when i was creating an npc. This can happens because as you can see in your gif when the character reaches waypoint it wait for the next waypoint to be generated and it takes some time for it that is why it pauses. To fix that issue you could delete the REACHED function or you could make so it would generate the path for the next waypoint as well.
For me deleting this ENTITYHUM.MoveToFinished:wait() worked but for you it may be different
I hope I helped