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()
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.
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.
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.
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.
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.
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.