New to pathfinding; annoying issue with player-chasing NPC

So I’m new to the pathfinding service in general, and in the process of scripting a pathfinding NPC that is intended to follow the player in a smart way, I did manage to get somewhere (as in: the NPC does follow the player). However, I continue to run into an annoying issue where the NPC appears to jitter about like it’s in some sort of existential confusion and panic about what exactly to do.

To better explain my troubles, I’ve provided a video here depicting the exact issue:

I’ve tried many methods that include “valid thread markers” using newproxy() (Since events to alert for path re-calculation occur on seperate threads which theoretically could’ve resulted in the other movement threads still operating with an outdated path), to upping the path regen cooldown significantly, to stuff that only god knows since honestly I can’t even remember given the vast quantity of methods I tried.

I even looked around on here and found that my issue could be fixed via SetNetworkOwner(nil), but I’ve tried that and it still didn’t really work.

Can anyone help? Here is the script version that I am using so far:

local pathfindingService = game:GetService("PathfindingService")
for i,v in pairs(script.Parent:GetChildren()) do
	if v:IsA("BasePart") then
		v:SetNetworkOwner(nil)
	end
end


local path = pathfindingService:CreatePath({
	Costs = {
		Ice = math.huge
	}
})


local startNewPath = false
local waypoints = {}
local currentWaypoint = 0
function generateNewPath(destination)
	if startNewPath then
		return false
	end
	
	path:ComputeAsync(script.Parent.PrimaryPart.Position, destination)
	startNewPath = true
	return true
end


spawn(function()
	local destination = nil
	while true do
		wait(1)
		if destination == script.Destination.Value then
			continue
		end
		
		if generateNewPath(script.Destination.Value) then
			destination = script.Destination.Value
		end
	end
end)

path.Blocked:Connect(function(index)
	if index > currentWaypoint then
		generateNewPath(script.Destination.Value)
	end
end)

while true do
	if startNewPath then
		currentWaypoint = 0
		waypoints = path:GetWaypoints()
		startNewPath = false
		continue
	else
		currentWaypoint += 1
	end
	
	local stationary = false
	if currentWaypoint > #waypoints then
		stationary = true
		currentWaypoint = #waypoints
	end
	
	local point = waypoints[currentWaypoint]
	if not stationary then
		if point.Action == Enum.PathWaypointAction.Jump then
			script.Parent.Humanoid.Jump = true
		end
		
		script.Parent.Humanoid:MoveTo(point.Position)
		script.Parent.Humanoid.MoveToFinished:Wait()
	else
		wait(0.1)
	end
end

So I think I fixed it. It might error on later so I’ll make sure to update my post, but for now I managed to fix the jittering problem.

Essentially what caused it was the fact that ComputeAsync() was being done on a seperate thread rather than the main one (which was intentional). I then made the path become visualized however and discovered that the reason the NPC was moving back and forth was because the path, due to being computed slightly in the past, had it’s first waypoint behind the current waypoint.

This then caused the NPC to move backwards as it tried to catch up on the new path, which then stacked as more paths got created, leaving the NPC a mess.

It still acts very hesitant though and walks very slowly, as if SetNetworkOwner wasn’t being done, but that might just be because of the fact that I’m running the server locally rather than through an actual roblox server.

Here’s the updated code for those that might experience my same heartache in the future:

local pathfindingService = game:GetService("PathfindingService")
function recursiveNetOwner(children)
	for i,v in pairs(children) do
		if v:IsA("BasePart") then
			pcall(function() v:SetNetworkOwner(nil) end)
		end
		recursiveNetOwner(v:GetChildren())
	end
end
recursiveNetOwner(script.Parent:GetChildren())


local path = pathfindingService:CreatePath({
	Costs = {
		Ice = math.huge
	}
})


local startNewPath = false
local pathRegenerated = false
local waypoints = {}
local currentWaypoint = 0
function generateNewPath()
	if startNewPath then
		return false
	end
	
	startNewPath = true
	pathRegenerated = true
	return true
end


spawn(function()
	local destination = nil
	while true do
		wait(1)
		if destination == script.Destination.Value then
			continue
		end
		
		if generateNewPath() then
			destination = script.Destination.Value
		end
	end
end)

path.Blocked:Connect(function(index)
	if index >= currentWaypoint then
		generateNewPath()
	end
end)

local holder = Instance.new("Model", workspace)
while true do
	if pathRegenerated then
		currentWaypoint = 0
		path:ComputeAsync(script.Parent.PrimaryPart.Position, script.Destination.Value)
		if path.Status == Enum.PathStatus.NoPath then
			generateNewPath()
			continue
		end
		
		waypoints = path:GetWaypoints()
		
		startNewPath = false
		pathRegenerated = false
		
		print("Continuing")
		continue
	else
		print("Incremented waypoint")
		currentWaypoint += 1
	end
	
	--[[holder:ClearAllChildren()
	for i,v in pairs(waypoints) do
		local p = Instance.new("Part", holder)
		p.Anchored = true
		p.CanCollide = false
		p.Size = Vector3.new(1,1,1)
		p.Position = v.Position

		if i == currentWaypoint then
			p.BrickColor = BrickColor.new("Really red")
		end
	end]]--
	
	local stationary = false
	if currentWaypoint > #waypoints then
		print("Stationary at "..#waypoints.." ("..currentWaypoint..")")
		stationary = true
		currentWaypoint = #waypoints
	end
	
	local point = waypoints[currentWaypoint]
	if not stationary then
		if point.Action == Enum.PathWaypointAction.Jump then
			print("Jump")
			script.Parent.Humanoid.Jump = true
		end
		
		print("Going")
		script.Parent.Humanoid:MoveTo(point.Position)
		print("Waiting to reach")
		if not script.Parent.Humanoid.MoveToFinished:Wait() then
			generateNewPath()
		end
	else
		wait(0.1)
	end
end
4 Likes