PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Nov 09 2018, 11:14 AM PST 15 min

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

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:

  1. The first section is similar to before: get the PathfindingService, set some variables, and create the Path object. The main additions are the waypoints and currentWaypointIndex variables. waypoints will store the computed waypoints and currentWaypointIndex 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
  1. The next function, followPath(), contains many of the same commands we used earlier, plus a little error checking on line 21. If Path/ComputeAsync|path:ComputeAsync() is successful, we get the waypoints and store them in the waypoints variable. Next, we set the currentWaypointIndex 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
  1. 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, the onWaypointReached() 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
  1. The final function, onPathBlocked(), is connected to the Path/Blocked|Blocked event on line 49. If the path is ever blocked, this function will fire and blockedWaypointIndex 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)

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!

Tags:
  • npc
  • path
  • pathfinding