Pathfinding Help

So I have a Roblox pathfinding script that I use for my horror monster npc but the problem is the pathfinding ai works well in an open environment map, however when it comes to my game map which is basically like a tunnel maze my pathfinding script breaks and the entity doesn’t follow the player that well. I don’t know that much about pathfinding but after reading some posts I think it has to do with the fact I’m using the humanoid:MoveTo() function, which is great in open environments. On top of the script breaking I’m not sure if my pathfinding AI is hogging too many resources, I tried to use events and Runservice.Heartbeat to avoid looping and it works well but it’s hard to set conditions to break the loop. I’m pretty sure I set my agent parameters correctly.
Solutions I’ve tried:
Tweening movement, Raycasting and Messing with debounce intervals.

local Pathfind = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local hrt = script.Parent:WaitForChild("HumanoidRootPart")
local animController = Humanoid:FindFirstChild("Animator")
local Folder = Instance.new("Folder")
Folder.Name = "Waypoints"
Folder.Parent = workspace

local targetPlayer = nil
local debounceTime = 0.02
local lastUpdateTime = 0.85

local pathUpdateEvent = Instance.new("BindableEvent")
local Target = workspace.PatrolPoints:GetChildren()

local isMoving = false
local currentDestination = nil

local Walk = script:FindFirstChild("Walk")
local Walktrack = Humanoid:LoadAnimation(Walk)

local Run = script:FindFirstChild("Run")
local RunTrack = Humanoid:LoadAnimation(Run)

local function PlayAnimation(animationId)
	local animation = Instance.new("Animation")
	animation.AnimationId = "rbxassetid://" .. tostring(animationId)

	local track = Humanoid:LoadAnimation(animation)
	track:Play()
	track:AdjustSpeed(1)
	track:Stop()
	return track
end



local function ShuffleTarget()
	if not isMoving then
		if #Target > 0 then
			local randompoint = math.random(1, #Target)
			local chosenpoint = Target[randompoint]
			currentDestination = chosenpoint.Position
			isMoving = true

			Humanoid.WalkSpeed = 20


			if animController then
				local currentanim = animController:GetPlayingAnimationTracks()
				if currentanim and currentanim.Animation == script.Run then
					RunTrack:Stop()
				else
					Walktrack:Play()
				end
			end

			local path2 = Pathfind:CreatePath({
				AgentRadius = Humanoid.RootPart.Size.X / 2,
				AgentHeight = Humanoid.HipHeight,
				AgentMaxSlope = 45,
				
				Costs = {
					Rock = 20
				}
			})

			path2:ComputeAsync(Humanoid.RootPart.Position, currentDestination)

			if path2.Status == Enum.PathStatus.Success then
				local waypoints = path2:GetWaypoints()

				for i, v in ipairs(waypoints) do
					local Point = Instance.new("Part")
					Point.Anchored = true
					Point.Shape = Enum.PartType.Ball
					Point.Size = Vector3.new(0.5, 0.5, 0.5)
					Point.Position = v.Position + Vector3.new(0, 2, 0)
					Point.CanCollide = false
					Point.BrickColor = BrickColor.new("Bright green")
					Point.Material = Enum.Material.Neon
					Point.Transparency = 0
					Point.Parent = Folder
					Point.Name = "Point" .. tostring(i)
				end

				for _, waypoint in ipairs(waypoints) do
					Humanoid:MoveTo(waypoint.Position)
					Humanoid.MoveToFinished:Wait()
				end
			end
			


			isMoving = false
		else
			print("No patrol points found.")
		end
	end
end


local function findNearestPlayer()
	local nearestPlayer = nil
	local nearestDistance = 200
	local players = Players:GetPlayers()

	for _, player in ipairs(players) do
		local character = player.Character
		if character and character ~= script.Parent and character:FindFirstChild("HumanoidRootPart") then
			local distance = (hrt.Position - character.HumanoidRootPart.Position).magnitude
			if distance < nearestDistance then
				nearestPlayer = character
				nearestDistance = distance
			end
		end
	end

	return nearestPlayer
end

local blockedconnection
local function updatePathToPlayer()
	targetPlayer = findNearestPlayer()
	if targetPlayer then

		local targetPosition = targetPlayer.HumanoidRootPart.Position

		if lastPlayerPos ~= targetPosition then
			lastPlayerPos = targetPosition

			local distanceToPlayer = (hrt.Position - targetPosition).magnitude
			if distanceToPlayer <= 200 then
				local Path = Pathfind:CreatePath({
					AgentRadius = Humanoid.RootPart.Size.X / 2,
					AgentHeight = Humanoid.HipHeight,
					AgentMaxSlope = 45,
					WaypointSpacing = 25
				})
				Path:ComputeAsync(hrt.Position, targetPosition)

				local Waypoints = Path:GetWaypoints()
				for _, point in ipairs(workspace.Waypoints:GetChildren()) do
					point:Destroy()
				end

				for i, waypoint in ipairs(Waypoints) do
					local Point = Instance.new("Part")
					Point.Anchored = true
					Point.Shape = Enum.PartType.Ball
					Point.Size = Vector3.new(0.5, 0.5, 0.5)
					Point.Position = waypoint.Position + Vector3.new(0, 2, 0)
					Point.CanCollide = false
					Point.BrickColor = BrickColor.new("Bright purple")
					Point.Material = Enum.Material.Neon
					Point.Transparency = 0
					Point.Parent = workspace.Waypoints
					Point.Name = "Point" .. tostring(i)
				end
				Humanoid.WalkSpeed = 40


				pathUpdateEvent:Fire(Waypoints)
				
				
			else
				for _, point in ipairs(workspace.Waypoints:GetChildren()) do
					point:Destroy()
				end
			end
		end
	else
		lastPlayerPos = nil

		for _, point in ipairs(workspace.Waypoints:GetChildren()) do
			point:Destroy()
		end
	end
end

local function MoveToWaypoint(waypoint)
	Humanoid:MoveTo(waypoint.Position)
	Humanoid.MoveToFinished:Wait()
end


pathUpdateEvent.Event:Connect(function(waypoints)
	for i, waypoint in ipairs(waypoints) do
		MoveToWaypoint(waypoint)
		Humanoid.MoveToFinished:Wait(2)

		if lastPlayerPos == nil or targetPlayer.Humanoid.Health == 0 then
			break
		end




	end


end)


local function Run()
	if animController then
		local currentanim = animController:GetPlayingAnimationTracks()
		if currentanim and currentanim.Animation == script.Walk then
			Walk:Stop()
		else
			return

		end
	end

	RunTrack:Play()

end

local function updateLoop()
	if lastPlayerPos == nil then
		ShuffleTarget()
	else

		Run()
	end
end

RunService.Heartbeat:Connect(function()
	local currentTime = os.clock()
	if currentTime - lastUpdateTime >= debounceTime then
		lastUpdateTime = currentTime
		updatePathToPlayer()
	end
	updateLoop()
end)



Tunnels and Open Env showcases

2 Likes

The Pathfinding is basically move to, if you use move to it uses Pathfinding service, try to use move to to pathfind to parts of your map, and if it fails it can’t find a valid path between the two points. Which would be why your npc’s pathfinding fails

Edit: perhaps its not correctly following the waypoints

Edit2: I think I know what’s happening, it’s following the waypoints against the wall and not the waypoints that point right to you

1 Like

yeah, do you have any suggestions on how to solve it? I honestly thought it had to do with the move to function but the way the path is also generated might play a role.

1 Like

Try This:

local Pathfind = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local hrt = script.Parent:WaitForChild("HumanoidRootPart")
local animController = Humanoid:FindFirstChild("Animator")
local Folder = Instance.new("Folder")
Folder.Name = "Waypoints"
Folder.Parent = workspace

local targetPlayer = nil
local debounceTime = 0.02
local lastUpdateTime = 0.85

local pathUpdateEvent = Instance.new("BindableEvent")
local Target = workspace.PatrolPoints:GetChildren()

local isMoving = false
local currentDestination = nil

local Walk = script:FindFirstChild("Walk")
local Walktrack = Humanoid:LoadAnimation(Walk)

local Run = script:FindFirstChild("Run")
local RunTrack = Humanoid:LoadAnimation(Run)

local function PlayAnimation(animationId)
	local animation = Instance.new("Animation")
	animation.AnimationId = "rbxassetid://" .. tostring(animationId)

	local track = Humanoid:LoadAnimation(animation)
	track:Play()
	track:AdjustSpeed(1)
	track:Stop()
	return track
end



local function ShuffleTarget()
	if not isMoving then
		if #Target > 0 then
			local randompoint = math.random(1, #Target)
			local chosenpoint = Target[randompoint]
			currentDestination = chosenpoint.Position
			isMoving = true

			Humanoid.WalkSpeed = 20


			if animController then
				local currentanim = animController:GetPlayingAnimationTracks()
				if currentanim and currentanim.Animation == script.Run then
					RunTrack:Stop()
				else
					Walktrack:Play()
				end
			end

			local path2 = Pathfind:CreatePath({
				AgentRadius = Humanoid.RootPart.Size.X / 2,
				AgentHeight = Humanoid.HipHeight,
				AgentMaxSlope = 45,

				Costs = {
					Rock = 20
				}
			})

			path2:ComputeAsync(Humanoid.RootPart.Position, currentDestination)

			if path2.Status == Enum.PathStatus.Success then
				local waypoints = path2:GetWaypoints()

				for i, v in ipairs(waypoints) do
					local Point = Instance.new("Part")
					Point.Anchored = true
					Point.Shape = Enum.PartType.Ball
					Point.Size = Vector3.new(0.5, 0.5, 0.5)
					Point.Position = v.Position + Vector3.new(0, 2, 0)
					Point.CanCollide = false
					Point.BrickColor = BrickColor.new("Bright green")
					Point.Material = Enum.Material.Neon
					Point.Transparency = 0
					Point.Parent = Folder
					Point.Name = "Point" .. tostring(i)
				end

				for _, waypoint in ipairs(waypoints) do
					Humanoid:MoveTo(waypoint.Position)
					Humanoid.MoveToFinished:Wait()
				end
			end



			isMoving = false
		else
			print("No patrol points found.")
		end
	end
end


local function findNearestPlayer()
	local nearestPlayer = nil
	local nearestDistance = 200
	local players = Players:GetPlayers()

	for _, player in ipairs(players) do
		local character = player.Character
		if character and character ~= script.Parent and character:FindFirstChild("HumanoidRootPart") then
			local distance = (hrt.Position - character.HumanoidRootPart.Position).magnitude
			if distance < nearestDistance then
				nearestPlayer = character
				nearestDistance = distance
			end
		end
	end

	return nearestPlayer
end

local blockedconnection
local function updatePathToPlayer()
	targetPlayer = findNearestPlayer()
	if targetPlayer then

		local targetPosition = targetPlayer.HumanoidRootPart.Position

		if lastPlayerPos ~= targetPosition then
			lastPlayerPos = targetPosition

			local distanceToPlayer = (hrt.Position - targetPosition).magnitude
			if distanceToPlayer <= 200 then
				local Path = Pathfind:CreatePath({
					AgentRadius = Humanoid.RootPart.Size.X / 2,
					AgentHeight = Humanoid.HipHeight,
					AgentMaxSlope = 45,
					WaypointSpacing = 25
				})
				Path:ComputeAsync(hrt.Position, targetPosition)

				local Waypoints = Path:GetWaypoints()
				for _, point in ipairs(workspace.Waypoints:GetChildren()) do
					point:Destroy()
				end

				for i, waypoint in ipairs(Waypoints) do
					local Point = Instance.new("Part")
					Point.Anchored = true
					Point.Shape = Enum.PartType.Ball
					Point.Size = Vector3.new(0.5, 0.5, 0.5)
					Point.Position = waypoint.Position + Vector3.new(0, 2, 0)
					Point.CanCollide = false
					Point.BrickColor = BrickColor.new("Bright purple")
					Point.Material = Enum.Material.Neon
					Point.Transparency = 0
					Point.Parent = workspace.Waypoints
					Point.Name = "Point" .. tostring(i)
				end
				Humanoid.WalkSpeed = 40


				pathUpdateEvent:Fire(Waypoints)


			else
				for _, point in ipairs(workspace.Waypoints:GetChildren()) do
					point:Destroy()
				end
			end
		end
	else
		lastPlayerPos = nil

		for _, point in ipairs(workspace.Waypoints:GetChildren()) do
			point:Destroy()
		end
	end
end

local function MoveToWaypoint(waypoint)
	Humanoid:MoveTo(waypoint.Position)
	Humanoid.MoveToFinished:Wait()
end


pathUpdateEvent.Event:Connect(function(waypoints)
	MoveToWaypoint(waypoints[3])
		Humanoid.MoveToFinished:Wait(2)

		if lastPlayerPos == nil or targetPlayer.Humanoid.Health == 0 then
			return nil
		end






end)


local function Run()
	if animController then
		local currentanim = animController:GetPlayingAnimationTracks()
		if currentanim and currentanim.Animation == script.Walk then
			Walk:Stop()
		else
			return

		end
	end

	RunTrack:Play()

end

local function updateLoop()
	if lastPlayerPos == nil then
		ShuffleTarget()
	else

		Run()
	end
end

RunService.Heartbeat:Connect(function()
	local currentTime = os.clock()
	if currentTime - lastUpdateTime >= debounceTime then
		lastUpdateTime = currentTime
		updatePathToPlayer()
	end
	updateLoop()
end)
1 Like

attempt to index nil with positioning, in both Humanoid:MoveTo(waypoint.Position)
and MoveToWaypoint(waypoints[3]).

im pretty sure since the path doesn’t update when the players positioning is still it goes over the 3rd waypoint and then it kind of just breaks i guess.

Nevermind, adding a simple path block modifier solved the problem.

1 Like

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