Pathfinding script not working properly (Solved)

spawn(function()
while true do
--code here
end
end)

All of the code?


Yes, you just replace that specific section, but keep the while loop.

Didnt work. This is what I did:




spawn(function()
	while true do
		wait()
		--code here

		local PathfindingService = game:GetService("PathfindingService")

		local Humanoid = script.Parent.Humanoid
		local Root = script.Parent:FindFirstChild("HumanoidRootPart")
		--local goal = game.Workspace.Test 
		local goal = game.Workspace:WaitForChild("trueblockhead101"):WaitForChild("HumanoidRootPart")
		local path = PathfindingService:CreatePath()
		local function UpdatePath()
			path:ComputeAsync(Root.Position, goal.Position)
			local waypoints = path:GetWaypoints()

			task.wait() 
			for i, waypoint in ipairs(waypoints) do
				path:ComputeAsync(Root.Position, goal.Position)
				local waypoints = path:GetWaypoints()

				Humanoid:MoveTo(waypoint.Position)

				if waypoint.Action == Enum.PathWaypointAction.Jump then
					Humanoid.Jump = true
				end
				Humanoid.MoveToFinished:Wait()
			end
		end
		while wait() do
			UpdatePath() 

		end

		goal:GetPropertyChangedSignal("Position"):Connect(UpdatePath) 

	end
end)

Not exactly. Instead, I want you to place it around the smaller loop.

1 Like

Same results, this is what I did:



		local PathfindingService = game:GetService("PathfindingService")

		local Humanoid = script.Parent.Humanoid
		local Root = script.Parent:FindFirstChild("HumanoidRootPart")
		--local goal = game.Workspace.Test 
		local goal = game.Workspace:WaitForChild("trueblockhead101"):WaitForChild("HumanoidRootPart")
		local path = PathfindingService:CreatePath()
		local function UpdatePath()
			path:ComputeAsync(Root.Position, goal.Position)
			local waypoints = path:GetWaypoints()

			task.wait() 
			for i, waypoint in ipairs(waypoints) do
				path:ComputeAsync(Root.Position, goal.Position)
				local waypoints = path:GetWaypoints()

				Humanoid:MoveTo(waypoint.Position)

				if waypoint.Action == Enum.PathWaypointAction.Jump then
					Humanoid.Jump = true
				end
				Humanoid.MoveToFinished:Wait()
			end
		end
	
spawn(function()
	while true do
		wait()
		UpdatePath() 
		end
	end)

goal:GetPropertyChangedSignal("Position"):Connect(UpdatePath)

Wait this is the wrong position. Let me just send you the finished product.

local PathfindingService = game:GetService("PathfindingService")

		local Humanoid = script.Parent.Humanoid
		local Root = script.Parent:FindFirstChild("HumanoidRootPart")
		--local goal = game.Workspace.Test 
		local goal = game.Workspace:WaitForChild("trueblockhead101"):WaitForChild("HumanoidRootPart")
		local path = PathfindingService:CreatePath()
		local function UpdatePath()
			path:ComputeAsync(Root.Position, goal.Position)
			local waypoints = path:GetWaypoints()
			for i, waypoint in ipairs(waypoints) do
				path:ComputeAsync(Root.Position, goal.Position)
				local waypoints = path:GetWaypoints()

				Humanoid:MoveTo(waypoint.Position)

				if waypoint.Action == Enum.PathWaypointAction.Jump then
					Humanoid.Jump = true
				end
				wait(3)--Change this.
			end
		end
	
UpdatePath()

Doesnt work, and doesnt update the path either…

I think I fixed the problem.
Here you go:

local PathfindingService = game:GetService("PathfindingService")

		local Humanoid = script.Parent.Humanoid
		local Root = script.Parent:FindFirstChild("HumanoidRootPart")
		--local goal = game.Workspace.Test 
		local goal = game.Workspace:WaitForChild("trueblockhead101"):WaitForChild("HumanoidRootPart")
		local path = PathfindingService:CreatePath()
		local function UpdatePath()
			path:ComputeAsync(Root.Position, goal.Position)
			local waypoints = path:GetWaypoints()
			for i, waypoint in ipairs(waypoints) do
				path:ComputeAsync(Root.Position, goal.Position)
				local waypoints = path:GetWaypoints()
				for _, waypoint in pairs(waypoints) do
					Humanoid:MoveTo(waypoint.Position)

					if waypoint.Action == Enum.PathWaypointAction.Jump then
						Humanoid.Jump = true
					end
					wait(3)--Change this.
				end
			end
		end
	
UpdatePath()
1 Like

Btw what should I change the wait(3) to?

Same resaults as last time, except now the npc looks a me for a second then looks back then looks at me and looks back and keeps doing that until it reaches its destination.

Any ides on what is wrong?

I’m not sure. I would rather visualize my waypoints by adding parts.

1 Like

Well ok thanks for trying!


There’s a few issues with your code. This is the code that I use which I adapted for your use case (I may have missed something that’s specific to me.). It may require some debugging/modifications/tweaks since I did this on the fly.

local PathfindingService = game:GetService("PathfindingService")
local Humanoid = script.Parent.Humanoid
local Root = script.Parent:FindFirstChild("HumanoidRootPart")
local goal = game.Workspace:WaitForChild("trueblockhead101"):WaitForChild("HumanoidRootPart")
local rand = Random.new(tick() / (time() + 1))
local baseJumpPower = 50
	
	
-- Pathfinding Data
-- Only need to create once.
local path = PathfindingService:CreatePath()
-- Waypoints Array
local waypoints = nil
-- Waypoints Array Index
local waypointsIndex = nil
-- Recompute needed flag
local recompute = true
-- Previous target position
local oldGoalPos = goal.Position -- Initial target position.
-- Blocked path event handler connection
local blockedConnection = nil
-- Rate limiting debouncer for moved event.
local debounce = true
-- Rate limiting debouncer for target selection.
local selectBounce = true
-- Spinlock so we don't try to update path while it's in the middle of an update.
local pathUpdating = false


-- Check to see if the target has significantly
-- changed position.  If the threshold has been
-- exceeded, then recompute the path.	
local function checkTargetPosition(target)
	if oldGoalPos == nil then
		-- First time running.
		recompute = true;
	else
		-- Check if the target's current position is greater
		-- than the threshold.  If so, then update the old
		-- position and recompute.
		if (oldGoalPos - target.Position).Magnitude > 16 then
			recompute = true
			oldGoalPos = target.Position
		end
	end
end

-- Perform the action at the given waypoint.
-- Index is used ONLY if there's a problem.
local function findPathAction(root, human, waypoint, index)
	if waypoint.Action == Enum.PathWaypointAction.Walk then
		human:MoveTo(waypoint.Position)
	elseif waypoint.Action == Enum.PathWaypointAction.Jump then
		human:MoveTo(waypoint.Position)
		human.Jump = true
	elseif waypoint.Action == Enum.PathWaypointAction.Custom then
		-- Insert custom code here (teleport?)
		utility.printTableRecursive(waypoint)
		if waypoint.Label == "BigJump" then
			local height = math.abs(waypoint.Position.Y -
				root.Position.Y) + 5
			local power = math.sqrt(2 * game.Workspace.Gravity * height)
			human.UseJumpPower = true
			human.JumpPower = power
			human:MoveTo(waypoint.Position)
			human.Jump = true
			while human.Jump == true do
				task.wait(0.1)
			end
			human.JumpPower = baseJumpPower
		elseif waypoint.Label == "FallDown" then
			human:MoveTo(waypoint.Position)
		elseif waypoint.Label == "Teleport" then
			root.Position = Vector3.new(waypoint.Position.X,
				waypoint.Position.Y + 4, waypoint.Position.Z)
			human:MoveTo(waypoint.Position)
		end
	else
		-- Something *REALLY* strange has happened.  Throw a warning.
		warn("Invalid waypoint action detected", "Action:",
			waypoint.Action, "Index:", index)
	end
end

-- Checks the recompute flag, if true performs a full
-- path recompute.
local function UpdatePath(npc, npcHuman, target)
	-- No need to recompute the path if it's already being
	-- computed 
	if pathUpdating then
		return
	end
	pathUpdating = true
	-- Recompute the path, if requested or needed.
	if recompute then
		-- Path compute *CAN* fail, so we need to wrap path:ComputeAsync
		-- in a pcall to catch that.		
		local success, failure = pcall(function()
			path:ComputeAsync(npc.Position, target.Position)
		end)

		if success and path.Status == Enum.PathStatus.Success then
			waypoints = path:GetWaypoints()

			-- Initialize the event handler to detect
			-- if the path becomes blocked.  If it does,
			-- this will request a recompute of the path.
			if blockedConnection == nil then
				
				-- Blocked path event handler is not active,
				-- activate it.
				
				blockedConnection = path.Blocked:Connect(function(blockIndex)
					-- Check if recompute is already requested.  if so,
					-- then disable the event handler.
					if recompute then
						-- Event handler will be reactivated on recompute.
						blockedConnection:Disconnect()
						blockedConnection = nil
					else
						-- Check if the obstacle is further down the path
						if blockIndex >= waypointsIndex then
							-- We have a block in the path, so we
							-- request a recompute and disconnect
							-- the blocked path event handler so
							-- we don't get spammed with block events.
							recompute = true
							blockedConnection:Disconnect()
							blockedConnection = nil
						end
					end
				end)
				
			end

			-- Start Moving
			-- Set index to 2 because 1 is the starting position.
			waypointsIndex = 2
			waypointAction(npc, npcHuman, waypoints[waypointsIndex], waypointsIndex)
			recompute = false
		else
			waypoints = nil
			recompute = true
			warn("Path did not compute: ", failure)
		end
	end
	pathUpdating = false
end


-- Event handler to handle when the NPC has reached
-- the target, or more than 8 seconds has elapsed since
-- the last MoveTo().
Humanoid.MoveToFinished:Connect(function(reached)
	if waypoints ~= nil then
		if reached then
			if waypointsIndex < #waypoints then
				-- Continue on to the next point
				waypointsIndex += 1
				waypointAction(Root, Humanoid, waypoints[waypointsIndex], waypointsIndex)
			else
				-- No more waypoints, request recompute and
				-- disable the blocked event handler.
				blockedConnection:Disconnect()
				blockedConnection = nil
				recompute = true
			end
		else
			-- Goal not reached, continue.
			waypointAction(Root, Humanoid, waypoints[waypointsIndex], waypointsIndex)
		end
	else
		-- Waypoints list is nil, request recompute.
		recompute = true
	end
end)

-- If the target moves between checks.
-- Note: Does *NOT* work if goal was moved by physics.
goal:GetPropertyChangedSignal("Position"):Connect(function ()
	if debounce then
		debounce = false
		checkTargetPosition(goal)
		UpdatePath(Root, Humanoid, goal)
		task.delay(0.2, function()
			debounce = true
		end)
	end
end)

-- Main Loop
UpdatePath(Root, Humanoid, goal)
selectBounce = true

while true do
	if Humanoid.Health <= 0 then
		-- NPC died, stop
		break;
	end
	
	-- Fast loop update
	checkTargetPosition(goal)
	UpdatePath(Root, Humanoid, goal)
	
	-- Slow loop update		
	if selectBounce then
		selectBounce = false	
		-- Put your target selection code here.
		
		-- Wait for at least 1 second and no more than
		-- 6 seconds.
		delay((rand:NextNumber() * 5) + 1, function()
			selectBounce = true
		end)
	end
	task.wait(0.1)
end

This was originally modeled from Roblox’s sample code with improvements that I made. The big issue that you are having that I think with it not updating is this line here:

goal:GetPropertyChangedSignal("Position"):Connect(UpdatePath)

The GetPropertyChangedSignal will not work if goal was moved by physics (CFrame updates). So a character that’s walking around under the control of a player will not cause that signal to fire. There are several posts on the forums regarding this.

So the only way to reliably follow a target is to poll the target position within a loop, checking if the target moved. If they did, then my code checks to see if they moved significantly. If they did, then it recomputes the path.

Explaination

The path is created (only needs to be done once). Then events are setup, the initial path is computed, and it enters the main loop. It checks if the NPC is still alive. If not, then no point in pathfinding. Then it checks to see if the target has moved significantly since the last position update. I have the distance as 16 studs because that’s the maximum distance that a player can travel in 1 second with the default walk speed of 16. It can be tweaked if needed. If the player did change position significanly, then the recompute flag is set to true. UpdatePath is called and checks that flag. If it’s true, it recomputes the path. If not, it just simply returns.

The next component of the mail loop is the target selection. This is the slow loop update and selection is ran between every 1 to 6 seconds. Target selection can be done many different ways. I’m using threat, nearest, and random. If a player is on the threat list, that player is locked until a different player creates more threat, that player dies, or the NPC dies. If the threat table is empty, then it picks the nearest player with a 10% chance of picking a random player instead.

UpdatePath

With UpdatePath, you want to get in and out as quickly as possible. No loitering around for this one. Its only job is to compute a new path and start the NPC on it. I have mutexes set so only thread can be in it at a time (Why recompute the path two or three times if only once will suffice?). The path is calculated and the blocked path event handler is activated. The blocked path event only fires if the path becomes blocked for some reason (Player moving/teleporting to an inaccessible location being the main reason.). If the path becomes blocked, it stops the NPC and requests a recompute by setting the recompute flag to true, and it deactivates itself so the server isn’t spammed by blocked events. As the NPC reaches each waypoint, the move to finished event fires which sets the NPC on to the next waypoint, if one exists. If there is no next waypoint, then a recompute is flagged. If the NPC has not reached the next waypoint (more than 8 seconds), then the NPC continues on. If there are no waypoints (waypoints is nil), then a recompute is flagged.

I know it seems like a significant amount of code, but it’s required for the path finding to work correctly. It’s coded in such a way as to help reduce lag on the server, which is of paramount importance. Hope this helps.

1 Like

Wow thanks!
I really appreciate that you took your time explaining this to me, but I get this weird error when I run your script.

 ▶ Path did not compute:  Workspace.Dummy.Pathfinding:78: attempt to index nil with 'Position' (x10)

Is this because the npc is a model and not a basepart?

The npc should be a HumanoidRootPart as it’s called with Root… Opps, I see the error. Let me fix it.

EDIT: Ok, it’s fixed. I forgot that I’m using three parameters instead of two. It’s NPC HumanoidRootPart, NPC Humanoid, Target HumanoidRootPart in that order. I missed a parameter.

1 Like

Well after testing out the script the problem still persists, the npc finishes its current path before starting a new one if the player starts moving somewhere else…

Reduce the threshold in checkTargetPosition() from 16 to something lower. 4 might be a good number. It will only recompute if the target’s traveled distance is above that number, or it reaches the last waypoint and recomputes there.

1 Like

Wow ok! Thanks for all the help!

No problem. Here’s a video with a boss that I have been working on that demonstrates my advanced pathfinding system. The place is my developer’s playground where I do all my development work and testing.

https://vimeo.com/725823693

When I jump up on the structure, notice that it stops. That’s the blocked path event firing. On the server it’s throwing a warning saying that the path did not compute because it couldn’t resolve the path to me.

Also notice that when I shot it, it was dancing around for a bit. That’s because in my weapons code, the bullet welds itself to the target until the debris service removes it after 10 seconds. Then it runs normally. That was not by design though. I think having a part welded to it messes with the animation or something. I might let it go as a feature because it adds some dynamic variety to the encounter.

I looked back on my code, and I have that threshold set to 4. It’s a balance. A lower number yields more accurate results, but at the cost of server performance since it has to do more work per unit of time.

1 Like