NPC Pathfinding not working correctly

It our game, we have an npc that the nearest player that is inside of the maze. However, sometimes it backtracks or just completletly stops.

This npc is crucial to our game, and if we don’t fix this, we cannot sponsor our game.

This is the code that controls the npc. It uses the SimplePath module instead of the normal Roblox pathfinding.

task.wait(0.5)

local ServerStorage = game:GetService("ServerStorage")
local SimplePath = require(ServerStorage.SimplePath)

--Define npc
local Dummy = script.Parent

local closestPlayer = nil

local closestMag = math.huge

-- Define a part called "Goal"

local agentParams = {
	AgentRadius = 2,
	AgentHeight = 8,
	AgentCanClimb = true,
	AgentCanJump = true,
}

--Create a new Path using the Dummy
local Path = SimplePath.new(Dummy, agentParams)

--Helps to visualize the path
Path.Visualize = false

local Goal = Vector3.new(0,0,0)

Path.Blocked:Connect(function()
	
	print("blocked")

		for i, plr in pairs(game.Players:GetChildren()) do

			local inPlim = false

			for i, plimObj in pairs(game.ServerStorage.PLIM:GetChildren()) do

				if plimObj.Value == plr.Name then

					inPlim = true

				end

			end

			if game.Workspace:FindFirstChild(plr.Name) and game.Workspace:FindFirstChild(plr.Name):FindFirstChild("HumanoidRootPart") and inPlim then

				if math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude) < closestMag or closestPlayer == nil then

					closestMag = math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude)

					closestPlayer = plr

				end

			end

		end

	if game.Workspace:FindFirstChild(closestPlayer.Name) then

		Goal = game.Workspace:FindFirstChild(closestPlayer.Name).HumanoidRootPart
		
	end

		Path:Run(Goal)
end)

--If the position of Goal changes at the next waypoint, compute path again
Path.WaypointReached:Connect(function()
	
	print("waypoint")

		for i, plr in pairs(game.Players:GetChildren()) do

			local inPlim = false

			for i, plimObj in pairs(game.ServerStorage.PLIM:GetChildren()) do

				if plimObj.Value == plr.Name then

					inPlim = true

				end

			end

			if game.Workspace:FindFirstChild(plr.Name) and game.Workspace:FindFirstChild(plr.Name):FindFirstChild("HumanoidRootPart") and inPlim then

				if math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude) < closestMag or closestPlayer == nil then

					closestMag = math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude)

					closestPlayer = plr

				end

			end

		end

	if game.Workspace:FindFirstChild(closestPlayer.Name) then

		Goal = game.Workspace:FindFirstChild(closestPlayer.Name).HumanoidRootPart
		
	end

	Path:Run(Goal)
end)

--Dummmy knows to compute path again if an error occurs
Path.Error:Connect(function(errorType)
	
	print("golem error")

		for i, plr in pairs(game.Players:GetChildren()) do

			local inPlim = false

			for i, plimObj in pairs(game.ServerStorage.PLIM:GetChildren()) do

				if plimObj.Value == plr.Name then

					inPlim = true

				end

			end

			if game.Workspace:FindFirstChild(plr.Name) and game.Workspace:FindFirstChild(plr.Name):FindFirstChild("HumanoidRootPart") and inPlim then

				if math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude) < closestMag or closestPlayer == nil then

					closestMag = math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude)

					closestPlayer = plr

				end

			end

		end
	
	if game.Workspace:FindFirstChild(closestPlayer.Name) then

		Goal = game.Workspace:FindFirstChild(closestPlayer.Name).HumanoidRootPart
		
	end
	
	Path:Run(Goal)
	
end)

Path.Reached:Connect(function()

	for i, plr in pairs(game.Players:GetChildren()) do

		local inPlim = false

		for i, plimObj in pairs(game.ServerStorage.PLIM:GetChildren()) do

			if plimObj.Value == plr.Name then

				inPlim = true

			end

		end

		if game.Workspace:FindFirstChild(plr.Name) and game.Workspace:FindFirstChild(plr.Name):FindFirstChild("HumanoidRootPart") and inPlim then

			if math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude) < closestMag or closestPlayer == nil then

				closestMag = math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude)

				closestPlayer = plr

			end

		end

	end

	if game.Workspace:FindFirstChild(closestPlayer.Name) then

		Goal = game.Workspace:FindFirstChild(closestPlayer.Name).HumanoidRootPart

	end

	Path:Run(Goal)
	
end)

	for i, plr in pairs(game.Players:GetChildren()) do

		local inPlim = false

		for i, plimObj in pairs(game.ServerStorage.PLIM:GetChildren()) do

			if plimObj.Value == plr.Name then

				inPlim = true

			end

		end

		if game.Workspace:FindFirstChild(plr.Name) and game.Workspace:FindFirstChild(plr.Name):FindFirstChild("HumanoidRootPart") and inPlim then

			if math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude) < closestMag or closestPlayer == nil then

				closestMag = math.abs(game.Workspace:FindFirstChild(plr.Name):WaitForChild("HumanoidRootPart").Position.Magnitude - script.Parent:WaitForChild("HumanoidRootPart").Position.Magnitude)

				closestPlayer = plr

			end

		end

	end

if game.Workspace:FindFirstChild(closestPlayer.Name) then

	Goal = game.Workspace:FindFirstChild(closestPlayer.Name).HumanoidRootPart

end

Path:Run(Goal)

Ask any questions about the script if anything isn’t clear!

Thank you!

~ NinjaFactory

:star2:

It sounds like the NPC is getting blocked and this is not working as you’d expect:

Path.Blocked:Connect(function()
      	print("blocked")
       ...
	Path:Run(Goal)
end)

Is blocked printing when the issue occurs?

No, it’s not printing. How could we fix this issue?

Then the issue may be in ServerStorage.SimplePath

Without debugging, I’d guess the issue may be in here:

--If the position of Goal changes at the next waypoint, compute path again
Path.WaypointReached:Connect(function()

Without knowing how your paths are being computed, hard to say what will happen when you change the “Goal” while potentially being in the midst of a set of waypoints.

I see. It looks like the issue you are experiencing may be caused by the way that the NPC is selecting the nearest player to follow.

One potential issue is that the NPC may be getting stuck in a loop where it continuously switches between two players that are close to each other, rather than selecting the player that is actually closest. This could happen if the NPC is not properly keeping track of which player it is currently following, and always selects the player with the lowest Magnitude value, regardless of whether or not it is the closest player.

To fix this issue, you could try keeping track of which player the NPC is currently following, and only switch to a new player if they are significantly closer than the current target. This could involve adding a new variable to the script that stores the current target player, and only updating it if the new player is significantly closer.

Additionally, you may want to consider using a different method for selecting the nearest player to follow. For example, you could use the DistanceFromSquared function to compare the distances between the NPC and each player, rather than using the Magnitude of their positions. This could be more accurate and help to avoid issues with the NPC getting stuck in a loop.

This could be the issue, if, this wasn’t all happening in single player, which it is. But this may be a problem for multiplayer.

If the NPC is only experiencing these issues in single player, it’s possible that the problem may be caused by something else in the script.

One thing you could try is adding some additional print statements to the script to see what is happening at different points in the execution. For example, you could print out the values of closestPlayer and closestMag at different points in the script to see if they are changing as expected. This could help you to understand what is causing the NPC to backtrack or stop moving.

It’s also possible that the issues you are experiencing may be related to the SimplePath module itself. If you are using an older version of the module, it may not be functioning as expected in your game. You could try updating to the latest version of the module to see if that resolves the issues you are experiencing.

It would also happen in multiplayer, but that doesn’t matter too much now. I found a makeshift solution to fix it for the time being, but I may look into it farther in the future. Thank you anyways! I may try some of your other advice to optimize the nearest player detection though!