Pathfinding is a system implemented by roblox to find the fastest path to a destination. We use the PathfindingService service. When you use that you'll get a table of waypoints where NPC will move to reach the destination.
For this tutorial, you'll need to create a destination and obstacles. You'll also need a NPC.
Here is an example
Here is the link of the NPC I used
https://www.roblox.com/library/3991217109/Simple-Animated-R6-NPC
PS: If you want the navigation mesh with arrows and parts walkable with Pathfinding colored purple, you need to go to File > Studio Settings > Studio (Tab) and you'll see a box named Show Navigation Mesh at the bottom in the category Visualization.
Well now you need to create a script inside the NPC. Name it whatever you like.
A new window appeared with a small code. Remove this the code inside the script.
First we have to initialize variables. We'll have to define the service, the destination, the NPC, the NPC's Humanoid and NPC's PrimaryPart. We'll also define three others variables which are Waypoints for the table of waypoints, IsBlocked to check if the current path has a waypoint blocked, if it has, then we restart the pathfinding and CurrentWaypoint for the current waypoint where the NPC has to move, this will also be useful when the path has a blocked point.
local PathfindingService = game:GetService("PathfindingService")
local NPC = script.Parent
local Torso = NPC:WaitForChild("Torso")
local Humanoid = NPC:WaitForChild("Humanoid")
local Destination = workspace:WaitForChild("Destination")
-- Replace with your own destination part.
local Waypoints = {}
local IsBlocked = false
local CurrentWaypoint = 2
To create a path you need to use the function CreatePath() in PathfindingService which has an argument AgentParameters. This argument is a table containing the settings of the path. In these settings you have in the order:
local Path = PathfindingService:CreatePath({
AgentRadius = Torso.Size.X, -- The size of his torso. Who cares about arms, because they can go through walls?
AgentHeight = NPC:GetExtentsSize().Y, -- This is the height of his body.
AgentCanJump = true, -- Can he jump? Yes he can.
AgentanClimb = true, -- Can he climb 1 stud high trussparts? Of course he can.
WaypointSpacing = 4, -- The 4 is the distance between each waypoint.
Costs = {
-- I'll talk about this in the PART 2 of this tutorial.
Grass = 0, -- I love grass, my life will be better if I can touch grass.
Water = math.huge -- I don't like water. I don't want to go through it.
}
})
Now we have to connect the path from the Start to the Destination using ComputeAsync() in Path (Async mean that this function will yield the current thread), the Start will be the current position of the Torso of the NPC and the Destination will be the destination that we set before. We'll create a new function and use protected call (pcall) to prevent for getting errors during computation.
local function ConnectPath()
local Success, Error = pcall(Path.ComputeAsync, Path, Torso.Position, Destination.Position)
if Success then
return true
end
return false
end
This function will return true if the computation has been a success or false is the computation has failed.
We to create an another function that will send you the nearest blocked waypoint which is CheckOcclusionAsync in Path. This function has one argument Start. This argument is the begining of the scan of each waypoint. It starts from the waypoint indicated by the Start argument and finishes to the last waypoint in the table Waypoints. This function contain also Async and this function yield for a long time, that can makes lag for our script. So we'll create an another thread later to prevent for yielding main thread (Script). We'll also use protected call.
local function GetNearestBlockedWaypoint()
local Success, NearestBlockedWaypoint = pcall(Path.CheckOcclusionAsync, Path, CurrentWaypoint) -- The scan will start from the CurrentWaypoint, because we don't care about old waypoints reached by NPC.
if Success and NearestBlockedWaypoint ~= -1 then
return NearestBlockedWaypoint
end
return -1
end
-1 mean that there are no waypoints blocked and the current path is safe.
Now we'll make the main function where we'll make the NPC move, jump if it's needed and re-compute path if it's blocked.
local function MoveWithPathfinding()
local IsConnected = ConnectPath()
if IsConnected and Path.Status == Enum.PathStatus.Success then -- Path.Status is an Enum. If it's Success, then the path is connected and we can gets waypoints if it's not Success that mean the path is connected but no ways to go to the destination.
Waypoints = Path:GetWaypoints() -- Getting waypoints.
IsBlocked = false -- We have to set to false everytime we call this function.
CurrentWaypoint = 2 -- Why 2 and not 1? 1 is the NPC's current position. We don't want him to go back to his starting point.
if Waypoints[CurrentWaypoint] then -- Check if the current waypoint exists in the table
if Waypoints[CurrentWaypoint].Action == Enum.PathWaypointAction.Jump then -- Check if the NPC has to jump to reach the current waypoint. You can remove that if you set "AgentCanJump" to false.
print("Jump is needed to reach this waypoint.")
Humanoid.Jump = true
end
Humanoid:MoveTo(Waypoints[CurrentWaypoint].Position) -- Moving to the current waypoint.
end
if IsBlocked then -- Check if it's blocked
print("A waypoint has been blocked, re-computing path.")
MoveWithPathfinding() -- If it's blocked, we re-compute the path.
end
else
print("Failed to connect path.")
end
end
We'll use Humanoid.MoveToFinished to check when the NPC reached the waypoint. This event contain one parameter Reached. When this is fired that mean that the NPC has reached the current waypoint or not. If he reached then we have to increment the CurrentWaypoint and check if the CurrentWaypoint isn't the last waypoint of the table Waypoints. We also have to check if a waypoint isn't blocked. If he didn't reach, we have to teleport him to the CurrentWaypoint.
Humanoid.MoveToFinished:Connect(function(Reached)
if Reached then -- CurrentWaypoint reached?
if CurrentWaypoint < table.maxn(Waypoints) then -- table.maxn(Waypoints) is the number of waypoints in the table. We have to check if it wasn't the last waypoint of the table Waypoint. If it was then he reached the destination.
CurrentWaypoint = CurrentWaypoint + 1 -- Going to the next waypoint
if IsBlocked then -- Check if it's blocked
print("A waypoint has been blocked, re-computing path.")
MoveWithPathfinding() -- If it's blocked, we re-compute the path.
end
if Waypoints[CurrentWaypoint] then -- Check if the current waypoint exists in the table
if Waypoints[CurrentWaypoint].Action == Enum.PathWaypointAction.Jump then -- Check if the NPC has to jump to reach the current waypoint. You can remove that if you set "AgentCanJump" to false.
print("Jump is needed to reach this waypoint.")
Humanoid.Jump = true
end
Humanoid:MoveTo(Waypoints[CurrentWaypoint].Position) -- Moving to the current waypoint.
end
else
print("Destination reached!")
end
else -- I failed to reach the CurrentWaypoint.
print("Failed to reach the CurrentWaypoint.")
NPC:PivotTo(CFrame.new(Waypoints[CurrentWaypoint].Position + Vector3.new(0, NPC:GetExtentsSize().Y / 2, 0))) -- Teleporting to the CurrentWaypoint.
MoveWithPathfinding()
end
end)
GetExtentSize() is a function that returns the size of the smallest bounding box that contains all of the BaseParts in the Model.
To make a new thread we'll use corountine.wrap and setup blocked path. We'll check if the BlockedWaypoint is near to the CurrentWaypoint in a loop.
coroutine.wrap(function()
while true do
if Waypoints[CurrentWaypoint] then
local NearestBlockedWaypoint = GetNearestBlockedWaypoint() -- Getting the nearest waypoint blocked
if NearestBlockedWaypoint ~= -1 then -- Check if the result isn't -1 (No waypoints blocked)
if CurrentWaypoint <= NearestBlockedWaypoint and CurrentWaypoint + 2 >= NearestBlockedWaypoint then -- We have to check if the BlockedWaypoint is near to the CurrentWaypoint. 2 is the number of waypoints to skip.
IsBlocked = true
end
end
end
task.wait()
end
end)()
This is the final part of the script. In this part we have to use SetNetworkOwner in the Torso and set to nil to prevent for getting lag and break pathfinding. We'll also call the MainFunction to start the pathfinding.
Torso:SetNetworkOwner(nil)
MoveWithPathfinding()
Here is the final result. Yellow spheres are waypoints where the NPC has to move and red spheres are waypoints where the NPC has to jump.
Thank you for reading through the tutorial. I hope this tutorial has helped you understand how pathfinding works. Also I'll probaby make others parts of this tutorial.
I want to tell you that this is one method of pathfinding, because there are others pathfinding functions like PathfindingService:FindPathAsync(Start, Finish) which is very similar to the function I told you about but there are no settings like AgentParameters. There are also deprecated functions like PathfindingService:ComputeRawPathAsync(Start, Finish, MaxDistance) or PathfindingService:ComputeSmoothPathAsync(Start, Finish, MaxDistance). These two functions are similar but deprecated. There where returning other Enum.PathStatus items like:
Here is an example DO NOT COPY THIS, JUST READ IT.
Enum.PathStatus.ClosestNoPath, -- When something block the path from the Start to the Finish, the Finish will be the place where the path has been blocked.
Enum.PathStatus.ClosestOutOfRange, -- When the distance between the Start and Finish is greater than the MaxDistance defined in the function.
Enum.PathStatus.FailStartNotEmpty, -- When the Start point is blocked and it's impossible to compute path.
Enum.PathStatus.FailFinishNotEmpty, -- When the Finish point is blocked and it's impossible to compute path.