Pathfinding AI stutters when it nearly approaches player

I have a problem where when the AI gets close enough to the player, it starts to stutter and never reaches the player. My only guess as to why this happens is due to the parameter requirements required to restart the function (thus the loop) don’t meet for one reason or another, ending the loop. And so, the only way for the AI to work again is if I force restart it, which is why it stutters for a bit.

Here is the code (Minus a few unrelated functions):

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")

local path = PathfindingService:CreatePath()

local dummyCharacter = script.Parent
local humanoid = dummyCharacter:WaitForChild("Humanoid")
local humRootPart = dummyCharacter.HumanoidRootPart

local waypoints
local blockedConnection
local finishedConnection

local objDistance

local currentWaypointIndex = 0

local function followPath(object)
	-- Compute the path
	local success, errorMessage = pcall(function()
		path:ComputeAsync(dummyCharacter.PrimaryPart.Position, object.HumanoidRootPart.Position)
	end)
	if success and path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints
		waypoints = path:GetWaypoints()
		local objectHumRoot = object.HumanoidRootPart
		local objectHum = object.Humanoid
		objDistance = (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude

		-- Detect if path becomes blocked
		blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			if blockedWaypointIndex >= currentWaypointIndex + 1 then
				blockedConnection:Disconnect()
				followPath(object)
			end
		end)

		-- Runs for every waypoint, telling dummy to move to them
		for index, waypoint in pairs(waypoints) do
			-- Skips the starting waypoint
			if index ~= 1 then
				-- Detect if dummy has reached target
				if (objectHumRoot.Position - humRootPart.Position).Magnitude <= 2 then
					attackPlayer(objectHum)
					followPath(object)
					return
				end
				
				--Move to the next waypoint
				currentWaypointIndex = index
				humanoid:MoveTo(waypoint.Position)
				humanoid.MoveToFinished:Wait()
				
				-- If dummy has lost track of target
				if (humRootPart.Position - objectHumRoot.Position).Magnitude > maxDistance or objectHum.Health <= 0 then
					-- Check if there's a closer target nearby
					if findTarget() then
						-- Follow target
						followPath(findTarget())
						return
					else
						-- Wander until a target is found
						repeat
							wanderAround()
						until findTarget()
						-- Follow target
						followPath(findTarget())
						return
					end
				end
				-- Detect if target changes position
				if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (objectHumRoot.Position - humRootPart.Position).Magnitude > 3 then
					followPath(object)
					return
				end
			end
		end
		
		print("Loop has finished running (Shouldn't ever happen)")
		
		followPath(object) -- I force restart it
		return
	else
		warn("Path not computed!", errorMessage)

		if findTarget() then
			-- Follow target
			followPath(findTarget())
			return
		else
			-- Wander until a target is found
			repeat
				wanderAround()
			until findTarget()
			-- Follow target
			followPath(findTarget())
			return
		end
	end
end

repeat
	wanderAround()
until findTarget()

followPath(findTarget())

Here is the video showcasing it:

If anyone can inform me of how I can solve the stuttering (or prevent the loop from exiting), it will be very appreciated.

1 Like

I assume that the lag here is actually the AI getting to the ‘last known position’ of your character according to the server. The server has a slight lag in data regarding where you’re moving your character.

You could use some delta math to project where the player is in real-time and then make the AI chase that projected position rather than the current position.

1 Like

Try setting your character’s HumanoidRootPart’s network ownership to the server.
HumanoidRootPart:SetNetworkOwner(nil)

If it doesn’t fix, try performing it on all base parts of the character:

for _, object in character:GetDescendants() do
    if object:IsA("BasePart") then
        object:SetNetworkOwner(nil)
    end
end

Do this initially, not in any loop. It might fix some stuttering.

1 Like

I assumed that setting the network ownership of your HRP would cause some dodgy UX for character movement. Would this not be an issue?

1 Like

No, it’s for the pathfinding AI. The AI is programmed by the server right? So it makes sense to set the ownership to the server. I’m not sure what you mean by dodgy UX but it has fixed stuttering for me in my own pathfinding AI. In fact, the SimplePath module does the HumanoidRootPart ownership code line.

1 Like

When the server has ownership of your character, you get input lag which is less than ideal for gameplay. I have quickly composed a comparison on what setting the NetworkOwnership to server vs client looks like for UX.

What you can do, however, is set the AI’s body network ownership to the server. As you mentioned, the AI is programmed on the server. It only makes sense for its body to be owned by the server, too.

1 Like

Yes, that’s my point. Don’t change your own character’s network ownership. It happens to be for some reason that the server doesn’t give the humanoid NPC server network ownership.

2 Likes

In which case I fully agree with your advise. I misunderstood your original post, apologies. Setting the network ownership to the server will indeed vastly improve AI behaviour.

1 Like

I think I found the real problem here, for anyone who still cares.

The AI stutters because of the force restart caused by the loop ending. The target reaches the player early which causes the force restart when near the player. The target reaches early because the conditions for the path to recompute to the actual player are not met, basically saying that the player hasn’t moved far away enough for the pathfinding to want to recompute. I wanted to set this bar lower but it just causes the pathfinding to be stuck eternally calculating because the player keeps moving.

If there’s any solution to that, it’d be appreciated.

Alright, I got it down, it works fine now.

I just needed to make it so that instead of having it all in the for loop, I enact it so that exiting the loop means that they’ve reached it.

local function followPath(object)
	-- Compute the path
	local success, errorMessage = pcall(function()
		path:ComputeAsync(dummyCharacter.PrimaryPart.Position, object.HumanoidRootPart.Position)
	end)
	if success and path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints
		waypoints = path:GetWaypoints()
		local objectHumRoot = object.HumanoidRootPart
		local objectHum = object.Humanoid
		objDistance = (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude

		-- Detect if path becomes blocked
		blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			if blockedWaypointIndex >= currentWaypointIndex + 1 then
				blockedConnection:Disconnect()
				followPath(object)
			end
		end)
		
		-- Runs for every waypoint, telling dummy to move to them
		table.remove(waypoints, 1)
		for index, waypoint in pairs(waypoints) do
			--Move to the next waypoint
			currentWaypointIndex = index
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
			
			-- If dummy has lost track of target
			if (humRootPart.Position - objectHumRoot.Position).Magnitude > maxDistance or objectHum.Health <= 0 then
				-- Check if there's a closer target nearby
				if findTarget() then
					-- Follow target
					followPath(findTarget())
					return
				else
					-- Wander until a target is found
					repeat
						wanderAround()
					until findTarget()
					-- Follow target
					followPath(findTarget())
					return
				end
			end
			-- Detect if target changes position
			if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (humRootPart.Position - objectHumRoot.Position).Magnitude >= 4 then
				followPath(object)
				return
			end
		end
		
		attackPlayer(objectHum)
		
		-- After dummy reaches target, wait for target to start moving again
		while wait() do
			if (objectHumRoot.Position - waypoints[#waypoints].Position).Magnitude - objDistance >= 2 and (humRootPart.Position - objectHumRoot.Position).Magnitude >= 4 then
				followPath(object)
				return
			else
				attackPlayer(objectHum)
			end
		end
	else
		warn("Path not computed!", errorMessage)

		if findTarget() then
			-- Follow target
			followPath(findTarget())
			return
		else
			-- Wander until a target is found
			repeat
				wanderAround()
			until findTarget()
			-- Follow target
			followPath(findTarget())
			return
		end
	end
end

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.