I’m working on an NPC using Roblox’s Navigation system, but the code is getting stuck while navigation. Specifically, the code is getting stuck on the Humanoid.MoveToFinished:Wait() event. The NPC stops moving and arrives at its waypoint, but it fails to fire the event and the code hangs until the max time expires and it moves forward one waypoint. I suspect that either the NPC is properly arriving at its waypoint and is stopping, but not firing the .MoveToFinished:Wait() event, or there is some kind of memory leak.
Here’s a video of the issue:
In the video, the NPC functions–normalish–until the player moves around the corner. At that time, it switches from raycasting and :MoveTo() to PathfindingService. While not visible in the video, the NPC had already used PathfindingService with no issue to get to the player, but when used again, it started to bug out.
Here’s the code that’s malfunctioning.
local function pathToTarget(char)
local position = monster.Position
local player = PlayerService:GetPlayerFromCharacter(char)
local targetPosition = char:WaitForChild("HumanoidRootPart").Position or char:WaitForChild("Torso").Position
path:ComputeAsync(position, targetPosition) --Create path
local waypoints = path:GetWaypoints()
for i, waypoint in pairs(waypoints) do --This loop makes the monster follow the waypoints to the player it is chasing.
print("Running Waypoints")
humanoid:MoveTo(waypoint.Position)
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
print("Waiting")
humanoid.MoveToFinished:Wait() --This code is getting stuck here. The NPC stops moving, but the event isn't fired.
print("Done")
end
return
end
I’ve tried looking online and I found a few related issues, but none had an answer and many were quite old. Additionally, the Developer Hub didn’t seem to have any useful information. Does anyone know why this is happening?
Edit: I managed to get it working, if you’re looking for a more detailed solution (with code) check post #8.
I had an issue similar to this when I made a humanoid moveto system for a car dealership thing I was working on. What I was able to do to actually solve this was embed a connection by doing something similar to this.
wConnection = humanoid.MoveToFinished:connect(function()
didReach = true
wConnection:Disconnect()
wConnection = nil
end)
-- freeze the movement of the player so it acts like an animation so to speak, and than unfreeze once the player actually leaves the place
humanoid:MoveTo(position)
-- create a new thread to check if the humanoid reaches and potentially check if they just straightup leave so their is no hanging connection
spawn(function()
while not didReach do
if not (humanoid and humanoid.Parent) then
break
end
print("still going")
wait(5)
humanoid:MoveTo(position)
end
if wConnection then
wConnection:Disconnect()
wConnection = nil
end
end)
This method kind of piggybacks off of what the documentation for moveto reccomends you do and i found it worked well.
I’m not sure if I’ve implemented this wrong, but this seems to break the entire navigation system. Instead of the NPC switching smoothly between AI navigations and simple Humanoid:MoveTo(player), the NPC no longer uses AI navigation, although it is running the code, and it is much buggier than before. The old code functions better, but it freezes at the Humanoid.MoveToFinished:Wait() line, making it unusable as well.
local function pathToTarget(char)
local position = monster.Position
local player = PlayerService:GetPlayerFromCharacter(char)
local targetPosition = char:WaitForChild("HumanoidRootPart").Position or char:WaitForChild("Torso").Position
path:ComputeAsync(position, targetPosition) --Create path
local waypoints = path:GetWaypoints()
for i, waypoint in pairs(waypoints) do --This loop makes the monster follow the waypoints to the player it is chasing.
print("Running Waypoints")
humanoid:MoveTo(waypoint.Position) --Moves NPC
if waypoint.Action == Enum.PathWaypointAction.Jump then --If jumping is required, make the NPC jump.
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
wConnection = humanoid.MoveToFinished:connect(function() --Honestly, I don't even know what this does.
print("Does something")
didReach = true
wConnection:Disconnect()
wConnection = nil
end)
spawn(function() --This spawns a corountine which "might" fix the error where humanoid.MoveToFinished:Wait() fails.
print("Function spawned")
while not didReach do
if not (humanoid and humanoid.Parent) then --I don't even know what this is supposed to do.
break
end
print("still going")
wait(5)
print("Trying again to move to position")
humanoid:MoveTo(waypoint.Position) --Try again to move to position
end
if wConnection then
print("Breaking Connection")
wConnection:Disconnect()
wConnection = nil
end
end)
print("Done")
--humanoid.MoveToFinished:Wait()
end
return
end
The NPC also appears to “stutter” or freeze at times, like it is lagging or something. Additionally, the code occasionally throws the error Workspace.NPC.Navigate:29: attempt to index nil with 'Disconnect'. Which is coming from this block of code:
wConnection = humanoid.MoveToFinished:connect(function() --Honestly, I don't even know what this does.
print("Does something")
didReach = true
wConnection:Disconnect() --This is throwing an error.
wConnection = nil
end)
The game will also hang under certain circumstances, and the NPC’s navigation fails. You can see that in this video:
If you need to see the code in context, I’ve included this script below.
--Variables
local PlayerService = game:GetService("Players")
local pathFindingService = game:GetService("PathfindingService")
local humanoid = script.Parent.Humanoid
local monster = script.Parent:FindFirstChild("HumanoidRootPart") or script.Parent:FindFirstChild("Torso")
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local PlayerTag = "PLAYER"
local path = pathFindingService:CreatePath()
local didReach = false
--Functions
local function pathToTarget(char)
local position = monster.Position
local player = PlayerService:GetPlayerFromCharacter(char)
local targetPosition = char:WaitForChild("HumanoidRootPart").Position or char:WaitForChild("Torso").Position
path:ComputeAsync(position, targetPosition) --Create path
local waypoints = path:GetWaypoints()
for i, waypoint in pairs(waypoints) do --This loop makes the monster follow the waypoints to the player it is chasing.
print("Running Waypoints")
humanoid:MoveTo(waypoint.Position) --Moves NPC
if waypoint.Action == Enum.PathWaypointAction.Jump then --If jumping is required, make the NPC jump.
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
wConnection = humanoid.MoveToFinished:connect(function() --Honestly, I don't even know what this does.
print("Does something")
didReach = true
wConnection:Disconnect()
wConnection = nil
end)
spawn(function() --This spawns a corountine which "might" fix the error where humanoid.MoveToFinished:Wait() fails.
print("Function spawned")
while not didReach do
if not (humanoid and humanoid.Parent) then --I don't even know what this is supposed to do.
break
end
print("still going")
wait(5)
print("Trying again to move to position")
humanoid:MoveTo(waypoint.Position) --Try again to move to position
end
if wConnection then
print("Breaking Connection")
wConnection:Disconnect()
wConnection = nil
end
end)
print("Done")
--humanoid.MoveToFinished:Wait()
end
return
end
local function raycastchase(target)
local ignorelist = {monster}
if target == nil then --If called without a target, return and try again.
return
end
local currentTarget = target:FindFirstChild("HumanoidRootPart") or target:FindFirstChild("Torso")
local v2root = currentTarget.Position - monster.Position
local ray = Ray.new(monster.Position, v2root.Unit * 800)
local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, ignorelist)
if hit then
if hit:IsDescendantOf(currentTarget.Parent) then --If the player is within sight, chase them directly.
humanoid:MoveTo(currentTarget.Position)
else -- If the player is not within signt, create a path and follow them.
pathToTarget(target)
end
end
return
end
--Looping Code (start here)
while true do
RunService.Heartbeat:Wait() --Ensures code is not run too frequently, but it should be updated as quickly as possible.
local min = math.huge
local char = nil
for i, character in pairs(CollectionService:GetTagged(PlayerTag)) do --Gets all living/targetable players in-game
local d = (character.PrimaryPart.Position - monster.Position).Magnitude --Determines the distance to each player
if d < min then
min = d
char = character
end
end
raycastchase(char) --Sends the closest player to the rest of the code.
end
Sorry about the huge chunk of code and stuff above, but I’m still not quite sure what exactly is malfunctioning, so I thought it best to include as much as possible.
I’m afraid not, I’m still unable to get this to work. Fortunately, I found that using raycasts, moving the NPC to the player when it can see it (or the players’ last known position if it can’t) works very well. However, if you still want it to navigate obstacles and navigate to a player that’s far away, I don’t have any solutions. If you find one though, please let me know.
I have never actually heard of network ownership before. I looked it up and implemented the code necessary to change it’s network ownership. This seems to have fixed the issue for the time being. I haven’t put it through thorough testing yet, but this seems to have fixed everything. Thank you!
For anyone who discovers this thread in the future looking for a solution, here’s the code I added at the beginning of my script.
for i, part in pairs(script.Parent:GetChildren()) do --Gets all children of the NPC
if part:IsA("BasePart") then --Checks that the child is a basepart.
part:SetNetworkOwner(nil) --Tells server to handle all physics.
end
end
Changing the network ownership seems to have fixed the issue I had with humanoid.MoveToFinished:Wait() for the time being. I still need to run some more tests, but hopefully, this solution will be permanent.
EDIT: i just realized this post is 2 years old lol. my bad, idk how i didnt notice honestly still, the bug that i mention in this post is a CURRENT issue, whoever is seeing this PLEASE post a bug report. this is ruining all of my NPCs.
i know that this is, ive been dealing with this bug all week and it is not the fault of your code. you see the way the NPC just randomly jittered in place? if a humanoid is almost at the point its moving to, and that point is above or below the HumanoidRootPart, then the humanoid will ocasionally just jitter out until the MoveTo() times out at ~8 seconds. because the remaining distance is almost perfectly vertical, it is trying to walk vertically. this is a problem introduced by recent updates, before this Roblox NPCs would not attempt to do this. its a rare situation, and you might think you have fixed it. but i can assure you that it is NOT fixed by setting network ownership. setting network ownership to server (nil) is not permanent, and it will often just reset back on the next physics frame, so it is doing practically nothing.
PLEASE, PLEASE, PLEASE SOMEONE MAKE A BUG REPORT FOR THIS (i can’t, as i have no posting permissions and no clue how this bug has gone so unnoticed)
another video showcasing the issue:
(note: you can see MoveTo() isnt being spammed because i put a print after every call to it, and it would be flooding the console way more)
if you make sure that the Y position of the MoveTo() target position is always the same as the RootPart’s Y position, the error is fixed.
my temporary workaround (use this instead of combined MoveTo() and MoveToFinished:Wait()):
function MoveToFixed(Humanoid, TargetPosition, Timeout, CancelEvent)
local Done = Instance.new("BindableEvent")
local SteppedConnection = nil
local HRP = Humanoid.RootPart
local LastY = (HRP ~= nil) and HRP.Position.Y or math.huge
local MoveToYAdjusted = (HRP ~= nil) and Vector3.new(TargetPosition.X, HRP.Position.Y, TargetPosition.Z) or Vector3.new(0,0,0)
Humanoid:MoveTo(MoveToYAdjusted)
SteppedConnection = RunService.Stepped:Connect(function()
if HRP then
if HRP.Position.Y ~= LastY then
LastY = HRP.Position.Y
MoveToYAdjusted = (HRP ~= nil) and Vector3.new(TargetPosition.X, HRP.Position.Y, TargetPosition.Z) or Vector3.new(0,0,0)
Humanoid:MoveTo(MoveToYAdjusted)
end
else
Done:Fire()
end
end)
Humanoid.MoveToFinished:Connect(function()
Done:Fire()
end)
task.delay(Timeout, function()
Done:Fire()
end)
if CancelEvent then
CancelEvent.Event:Connect(function()
Done:Fire()
end)
end
Done.Event:Wait()
if SteppedConnection then
SteppedConnection:Disconnect()
end
end