I’ve been working with ControllerManager
too, and I totally get your frustration — it doesn’t have a built-in .MoveToFinished
event like Humanoid:MoveTo()
, so detecting arrival isn’t as straightforward.
But don’t worry! Here are a few solid strategies that can help make this more reliable:
- Use a Region-Based Arrival Check (Better Than Magnitude)
Instead of checking raw .Magnitude
every frame (which can be noisy due to slight model jitters), try defining a small radius or region and check if the NPC’s HumanoidRootPart
is within that area.
Here’s a simple example:
local ARRIVAL_RADIUS = 2
function hasReachedPosition(npc, targetPosition)
local root = npc:FindFirstChild("HumanoidRootPart")
if root then
local horizontalDist = (Vector3.new(targetPosition.X, 0, targetPosition.Z) - Vector3.new(root.Position.X, 0, root.Position.Z)).Magnitude
local verticalDist = math.abs(targetPosition.Y - root.Position.Y)
return horizontalDist <= ARRIVAL_RADIUS and verticalDist <= 3
end
return false
end
Call this check in a loop or use RunService.Heartbeat
until it’s true.
- Use Waypoints from PathfindingService
If you’re using PathfindingService
, it gives you waypoints. You can move the NPC toward each one, and after each movement, use the arrival check above.
for i, waypoint in ipairs(path:GetWaypoints()) do
controllerManager:MoveTo(waypoint.Position)
repeat
RunService.Heartbeat:Wait()
until hasReachedPosition(npc, waypoint.Position)
end
This mimics Humanoid:MoveTo()
behavior pretty closely and works well with ControllerManager.
- ControllerManager Doesn’t Have Events — So You Need Polling
Unfortunately, as of now, ControllerManager
doesn’t fire events like MoveToFinished
. So yes — polling (looping + checking position) is currently the safest method unless Roblox adds more event-driven APIs in the future.
You can optimize your polling to avoid checking every frame — maybe check every 0.1
seconds using task.wait(0.1)
instead of RunService.Heartbeat
.
Bonus Tip: Add Debug Visuals!
When debugging NPC movement, it’s super helpful to visualize waypoints and destination zones using Part
instances or drawing tools like DrawingService
(via plugins).
Summary
• Use a radius-based position check (instead of raw magnitude every frame).
• For pathfinding, loop through waypoints and wait until each one is reached.
• Since ControllerManager lacks movement events, polling is necessary — but you can do it smartly.