Creating NPCs that can intelligently navigate your game world is one of those features that makes your Roblox game feel truly alive. Whether you’re building a zombie survival game, a chase sequence, or helpful quest-givers, understanding PathfindingService is essential. In this tutorial, we’ll dive deep into how pathfinding works in Roblox and build a complete NPC that can chase players around obstacles.
What is PathfindingService?
PathfindingService is Roblox’s built-in solution for calculating routes between two points while avoiding obstacles. Instead of NPCs walking straight through walls or getting stuck on objects, PathfindingService analyzes your game’s geometry and finds a walkable path.
The service uses an algorithm to examine the 3D space and creates a series of waypoints—coordinates the NPC should walk toward in sequence. Think of it like GPS navigation: you get turn-by-turn directions rather than just pointing toward your destination.
Setting Up Your NPC
Before we implement pathfinding, we need a basic NPC structure. For this tutorial, we’ll assume you have a Rig (R15 or R6) in the Workspace. Let’s start with the foundation:
local PathfindingService = game:GetService("PathfindingService")
local npc = workspace.NPC -- Your NPC model
local humanoid = npc:WaitForChild("Humanoid")
local rootPart = npc:WaitForChild("HumanoidRootPart")
local function findNearestPlayer()
local nearestPlayer = nil
local shortestDistance = math.huge
for _, player in pairs(game.Players:GetPlayers()) do
if player.Character then
local character = player.Character
local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
if humanoidRootPart then
local distance = (rootPart.Position - humanoidRootPart.Position).Magnitude
if distance < shortestDistance then
shortestDistance = distance
nearestPlayer = player
end
end
end
end
return nearestPlayer
end
This code establishes references to the core services and components we'll need. The findNearestPlayer() function will help our NPC decide who to chase—useful for games with multiple players.
Creating a Path
Now for the exciting part: creating an actual path. PathfindingService works by creating a Path object that you can compute and then extract waypoints from:
local function createPath()
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentCanClimb = false,
WaypointSpacing = 4,
Costs = {
Water = 20
}
})
return path
end
Let's break down these parameters because they're crucial to how your NPC behaves:
- AgentRadius: How wide your NPC is. A larger radius means the path will avoid narrower gaps.
- AgentHeight: The height of your NPC. Important for ensuring paths don't go under low ceilings.
- AgentCanJump: Whether the NPC can jump over obstacles. Setting this to true allows more complex paths.
- AgentCanClimb: Whether the NPC can use TrussParts to climb. Useful for platformer-style games.
- WaypointSpacing: Distance between waypoints. Lower values create smoother but more computation-heavy paths.
- Costs: A table that assigns "costs" to different materials or regions. Higher costs make the pathfinding avoid those areas when possible.
Computing and Following a Path
Creating a path object is just the first step. We need to compute a specific route to a target, then make our NPC follow it:
local function followPath(destination)
local path = createPath()
local success, errorMessage = pcall(function()
path:ComputeAsync(rootPart.Position, destination)
end)
if not success then
warn("Path computation failed: " .. errorMessage)
return
end
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for i, waypoint in ipairs(waypoints) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
else
warn("Path not computed successfully")
end
end
This is where the magic happens! Let's understand the flow:
- We create a path object with our desired parameters
- We use
ComputeAsync()wrapped in pcall for error handling—path computation can fail if positions are invalid - We check if the path status is successful
- We retrieve waypoints and iterate through them
- For each waypoint, we check if jumping is required and make the humanoid move to that position
- We wait for
MoveToFinishedbefore proceeding to the next waypoint
The MoveToFinished event is crucial—without waiting for it, the NPC would try to move to all waypoints simultaneously, which doesn't work.
Making It Continuous
Right now, our NPC follows a path once and stops. Let's make it continuously chase the nearest player:
local function startChasing()
while task.wait(0.5) do
local targetPlayer = findNearestPlayer()
if targetPlayer and targetPlayer.Character then
local targetRoot = targetPlayer.Character:FindFirstChild("HumanoidRootPart")
if targetRoot then
local distance = (rootPart.Position - targetRoot.Position).Magnitude
-- Only pathfind if player is within range but not too close
if distance > 5 and distance < 100 then
followPath(targetRoot.Position)
elseif distance <= 5 then
-- Stop moving if too close
humanoid:MoveTo(rootPart.Position)
end
end
end
end
end
-- Start the chasing behavior
startChasing()
This creates a loop that recalculates the path every 0.5 seconds. We add distance checks to avoid unnecessary pathfinding when the player is very close or too far away.
Handling Path Blocking
Sometimes paths become blocked while the NPC is following them—a door closes, a platform moves, or terrain changes. We should handle this gracefully:
local function followPathWithBlocking(destination)
local path = createPath()
local success, errorMessage = pcall(function()
path:ComputeAsync(rootPart.Position, destination)
end)
if not success or path.Status ~= Enum.PathStatus.Success then
return false
end
local waypoints = path:GetWaypoints()
local blockedConnection
local reachedConnection
local pathBlocked = false
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
if blockedWaypointIndex > 1 then
pathBlocked = true
end
end)
for i, waypoint in ipairs(waypoints) do
if pathBlocked then
break
end
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
blockedConnection:Disconnect()
return not pathBlocked
end
The path.Blocked event fires when the path becomes obstructed. By connecting to this event, we can exit the pathfinding loop early and recalculate a new path in the main loop.
Optimizing Performance
Pathfinding can be computationally expensive, especially with many NPCs. Here are key optimization strategies:
- Increase wait time between recalculations: Not every NPC needs to recalculate every frame. 0.5-1 second intervals work well.
- Use distance checks: Only pathfind when targets are within a reasonable range.
- Reuse path objects when possible: Though creating them is cheap, it's still good practice.
- Increase WaypointSpacing: For large, open areas, you don't need waypoints every 2 studs.
- Limit active NPCs: Consider deactivating pathfinding for NPCs far from any player.
Common Pitfalls to Avoid
Through my experience with PathfindingService, here are mistakes I see frequently:
- Not using pcall: ComputeAsync can error out, always wrap it in pcall
- Forgetting to wait for MoveToFinished: This creates very buggy movement
- Wrong AgentRadius/Height: Measure your NPC and set these accurately or paths will look unnatural
- Pathfinding through walls: Ensure your walls have CanCollide enabled and aren't in folders that PathfindingService ignores
- Not checking Path.Status: Always verify the path computed successfully before using waypoints
Recap and Next Steps
Congratulations! You've learned how to create NPCs that intelligently navigate your game world using PathfindingService. You now understand how to create paths with custom parameters, compute routes to targets, handle waypoints including jumps, manage path blocking, and optimize for performance.
To build on this foundation, consider exploring:
- Path modifiers and regions: Create zones that NPCs prefer or avoid using PathfindingModifiers
- Custom NPC states: Implement patrol, chase, and search behaviors with state machines
- Smart target selection: Add line-of-sight checks so NPCs only chase players they can "see"
- Advanced movement: Combine pathfinding with custom animations for more lifelike NPCs
Keep experimenting, and remember that great NPC behavior often comes from combining pathfinding with other game logic. Happy scripting!