Pathfinding
Pathfinding
In the Articles/Moving NPCs Between Points|Moving NPCs Between Points
tutorial, you learned about direct straight-line character movement. In this article, we’ll explore how to move an NPC along a more complex path or around obstacles. This is known as pathfinding.
Setup
In Roblox, pathfinding is driven by the PathfindingService
, so your scripts must get the service before doing much else.
local PathfindingService = game:GetService("PathfindingService")
Creating a Path
Once you’ve included PathfindingService
in your script, you can create a new Path
with the PathfindingService/CreatePath|CreatePath()
method:
local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath(pathParams)
Path Parameters
As you can see, this method accepts one argument, pathParams
, a Lua table which lets you fine-tune the path for the size of the agent (the humanoid that will move along the path).
Here are the available parameters for the pathParams
table:
Key | Value Type | Default | Description |
---|---|---|---|
AgentRadius | integer | 2 | Humanoid radius. Used to determine the minimum separation from obstacles. |
AgentHeight | integer | 5 | Humanoid height. Empty space smaller than this value, like the space under stairs, will be marked as non-traversable. |
Moving Along the Path
Let’s put pathfinding into action! The zombie below has a few more brains than the zombie in the Articles/Moving NPCs Between Points|direct movement
tutorial, so it shouldn’t walk directly toward the pink flag and into the lava. Instead, let’s make it walk safely across the plank.

The following code gets the PathfindingService
, creates variables for the zombie and its Humanoid
, sets the destination point (pink flag), and creates the Path
object.
local PathfindingService = game:GetService("PathfindingService") -- Variables for the zombie, its humanoid, and destination local zombie = game.Workspace.Zombie local humanoid = zombie.Humanoid local destination = game.Workspace.PinkFlag -- Create the path object local path = PathfindingService:CreatePath()
PathfindingService/CreatePath|CreatePath()
because the agent (zombie) is a normal-sized character and the default radius/height values are fine.
Computing the Path
After you’ve created a valid Path
object using PathfindingService/CreatePath|CreatePath()
, you need to compute the path — this is an explicit step that does not happen automatically when the path is created!
To compute a path, call Path/ComputeAsync|ComputeAsync()
on the Path
object, providing a Vector3
for both the starting location and target destination.
local PathfindingService = game:GetService("PathfindingService")
-- Variables for the zombie, its humanoid, and destination
local zombie = game.Workspace.Zombie
local humanoid = zombie.Humanoid
local destination = game.Workspace.PinkFlag
-- Create the path object
local path = PathfindingService:CreatePath()
-- Compute the path
path:ComputeAsync(zombie.HumanoidRootPart.Position, destination.PrimaryPart.Position)
Getting Path Waypoints
Once the Path
is computed by Path/ComputeAsync|ComputeAsync()
, it will have a series of waypoints that, when followed, can lead a character along the path. These points are gathered with the Path/GetWaypoints|GetWaypoints()
function:
local PathfindingService = game:GetService("PathfindingService")
-- Variables for the zombie, its humanoid, and destination
local zombie = game.Workspace.Zombie
local humanoid = zombie.Humanoid
local destination = game.Workspace.PinkFlag
-- Create the path object
local path = PathfindingService:CreatePath()
-- Compute the path
path:ComputeAsync(zombie.HumanoidRootPart.Position, destination.PrimaryPart.Position)
-- Get the path waypoints
local waypoints = path:GetWaypoints()
Showing Waypoints
With the waypoints saved, we can show each one by creating a small part at its location:
-- Get the path waypoints
local waypoints = path:GetWaypoints()
-- Loop through waypoints
for _, waypoint in pairs(waypoints) do
local part = Instance.new("Part")
part.Shape = "Ball"
part.Material = "Neon"
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position
part.Anchored = true
part.CanCollide = false
part.Parent = game.Workspace
end

As you can see, the path waypoints lead across the plank and over to the pink flag!
Path Movement
The path looks good, so let’s make the zombie walk along it. The easiest way is to call Humanoid/MoveTo|MoveTo()
from waypoint to waypoint. In this script, we can simply add two commands to the same waypoint loop:
-- Loop through waypoints
for _, waypoint in pairs(waypoints) do
local part = Instance.new("Part")
part.Shape = "Ball"
part.Material = "Neon"
part.Size = Vector3.new(0.6, 0.6, 0.6)
part.Position = waypoint.Position
part.Anchored = true
part.CanCollide = false
part.Parent = game.Workspace
-- Move zombie to the next waypoint
humanoid:MoveTo(waypoint.Position)
-- Wait until zombie has reached the waypoint before continuing
humanoid.MoveToFinished:Wait()
end
Handling Blocked Paths
Many Roblox worlds are dynamic — parts might move or fall, floors collapse, etc. This can block a computed path and prevent the NPC from reaching its destination.
To handle this, you can connect the Path/Blocked|Blocked
event to the Path
object and re-compute the path around whatever blocked it. Consider this pathfinding script:
local PathfindingService = game:GetService("PathfindingService") -- Variables for the zombie, its humanoid, and destination local zombie = game.Workspace.Zombie local humanoid = zombie.Humanoid local destination = game.Workspace.PinkFlag -- Create the path object local path = PathfindingService:CreatePath() -- Variables to store waypoints table and zombie's current waypoint local waypoints local currentWaypointIndex local function followPath(destinationObject) -- Compute and check the path path:ComputeAsync(zombie.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position) -- Empty waypoints table after each new path computation waypoints = {} if path.Status == Enum.PathStatus.Success then -- Get the path waypoints and start zombie walking waypoints = path:GetWaypoints() -- Move to first waypoint currentWaypointIndex = 1 humanoid:MoveTo(waypoints[currentWaypointIndex].Position) else -- Error (path not found); stop humanoid humanoid:MoveTo(zombie.HumanoidRootPart.Position) end end local function onWaypointReached(reached) if reached and currentWaypointIndex < #waypoints then currentWaypointIndex = currentWaypointIndex + 1 humanoid:MoveTo(waypoints[currentWaypointIndex].Position) end end local function onPathBlocked(blockedWaypointIndex) -- Check if the obstacle is further down the path if blockedWaypointIndex > currentWaypointIndex then -- Call function to re-compute the path followPath(destination) end end -- Connect 'Blocked' event to the 'onPathBlocked' function path.Blocked:Connect(onPathBlocked) -- Connect 'MoveToFinished' event to the 'onWaypointReached' function humanoid.MoveToFinished:Connect(onWaypointReached) followPath(destination)
Several things have been added or changed, so let’s walk through the code:
- The first section is similar to before: get the
PathfindingService
, set some variables, and create thePath
object. The main additions are thewaypoints
andcurrentWaypointIndex
variables.waypoints
will store the computed waypoints andcurrentWaypointIndex
will track the index number of each waypoint the zombie reaches, starting at 1 and increasing as it walks along the path.
local PathfindingService = game:GetService("PathfindingService")
-- Variables for the zombie, its humanoid, and destination
local zombie = game.Workspace.Zombie
local humanoid = zombie.Humanoid
local destination = game.Workspace.PinkFlag
-- Create the path object
local path = PathfindingService:CreatePath()
-- Variables to store waypoints table and zombie's current waypoint
local waypoints
local currentWaypointIndex
- The next function,
followPath()
, contains many of the same commands we used earlier, plus a little error checking on line 21. IfPath/ComputeAsync|path:ComputeAsync()
is successful, we get the waypoints and store them in thewaypoints
variable. Next, we set thecurrentWaypointIndex
counter to 1 and move the zombie to the first waypoint.
local function followPath(destinationObject) -- Compute and check the path path:ComputeAsync(zombie.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position) -- Empty waypoints table after each new path computation waypoints = {} if path.Status == Enum.PathStatus.Success then -- Get the path waypoints and start zombie walking waypoints = path:GetWaypoints() -- Move to first waypoint currentWaypointIndex = 1 humanoid:MoveTo(waypoints[currentWaypointIndex].Position) else -- Error (path not found); stop humanoid humanoid:MoveTo(zombie.HumanoidRootPart.Position) end end
- In a dynamic place where paths may be blocked, it’s problematic to loop through all waypoints in a
pairs()
loop. If anything blocks the path, that loop can be tricky to stop/break. In this script, theonWaypointReached()
function moves the zombie onward only after the next waypoint is reached.
local function onWaypointReached(reached) if reached and currentWaypointIndex < #waypoints then currentWaypointIndex = currentWaypointIndex + 1 humanoid:MoveTo(waypoints[currentWaypointIndex].Position) end end
- The final function,
onPathBlocked()
, is connected to thePath/Blocked|Blocked
event on line 49. If the path is ever blocked, this function will fire andblockedWaypointIndex
will be the waypoint index number that was blocked.
local function onPathBlocked(blockedWaypointIndex) -- Check if the obstacle is further down the path if blockedWaypointIndex > currentWaypointIndex then -- Call function to re-compute the path followPath(destination) end end -- Connect 'Blocked' event to the 'onPathBlocked' function path.Blocked:Connect(onPathBlocked)
Path/Blocked|Blocked
event can fire on a path anytime during the path's lifetime. It may also fire multiple times if moving obstacles are involved, like a boulder rolling across the path. Because of this, you may want to Instance/Destroy|destroy
the path or un-register the Path/Blocked|Blocked
connection until a new path is computed.
As you can see, PathfindingService
lets you create NPCs that are much smarter than the average zombie. Along with custom agent parameters and the Path/Blocked|Blocked
event, you can make any NPC reach a destination, even in a dynamic and changing place!