To my beloved old readers of this post
Please read this post of this topic to understand why I had decided to revamp this whole tutorial.
How To Use Roblox Pathfinding Service 2.0 - #6 by ItzMeZeus_IGotHacked
PATHFINDING SERSVICE TUTORIAL 2.0 IS FINALLY HERE!
Ladies and gentlemen welcome back to another Roblox scripting tutorial where in today’s tutorial we are going to talk about how you can use Pathfinding service better
in Roblox!
Let’s see where we left off last time…
Oh.
It’s been more than a year since this topic was mentioned on here…
December 20th
Well, it’s been a year since I said that and everyone was wondering where is it…
Well here it is! I was just being a lazy couch potato and can’t think of new ideas on how to make this more interesting, until some new additions to the pathfinding service, such as the new Pathfinding Modifiers.
So buckle your seatbelts up everyone. because today we are going to learn:
- Basics of PathfindingService
- Handling path blocked events
- Agent Parameters and Pathfinding Modifiers
- Bot AI Chasing (COMING SOON)
- Explaining why my old tutorial about Pathfinding Service is bad
- Parallel luau + Pathfinding Service (MAYBE…)
If this is your first time learning Pathfinding service, you can still read this tutorial as I’ve planned this tutorial to rework on the old one since the old script from the old tutorial is inefficient and buggy… heh. Without any further ado, let’s get rolling!
Getting started
This section will cover the nitty-gritty of utilising PathfindingService
to its maximum. At the end of this section, you will be able to script a humanoid that can walk to a certain position and smartly create a new path to the position again if it is blocked by another part when traversing through the path.
- Get a working R6 or R15 dummy model in your game. You can do this by using the Rig Builder plugin provided by Roblox Studio default.
im gonna use r15 in this case
-
Most important step, unanchor every single BaseParts in the dummy model. This is to ensure that the model can walk without any anchored parts connecting it to prevent it from walking. You can simply click the model in the explorer tab and unanchor the model.
-
Insert a server script in the model and declare a few variables such as referring Pathfinding service itself, the model, humanoid instance and HumanoidRootPart.
local PathfindingService = game:GetService("PathfindingService")
local model = script.Parent
local humanoid = model.Humanoid
local humanoidRootPart = model.HumanoidRootPart
- Now, we will use
PathfindingService:CreatePath()
function. As its name suggests, it basically creates aPath
instance which we are going to use. Don’t mind theagentParameters
argument in the function. We will cover that later. We are also going to define some constants which is useful for our functions soon. We will also declare a variable namedreachedConnection
which will be useful soon.
local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local model = script.Parent
local humanoid = model.Humanoid
local humanoidRootPart = model.HumanoidRootPart
local path = PathfindingService:CreatePath()
local reachedConnection
– REVAMP STARTING HERE –
- Create a function named
walkTo()
. This function will be the brain of the operation of this whole thing. Add two parameters,targetPosition
andyieldable
(determines whether or not to yield the script until the humanoid has reached the destination.) After creating the function, call it with the respective arguments. I have created a base part namedEndGoal
under workspace.
local function walkTo(targetPosition, yieldable)
end
walkTo(workspace.EndGoal.Position, true)
- Now let’s dive into the main part! To create or compute the path between two points, we have to use
path:ComputeAsync()
on thepath
instance we have created throughPathfindingService:CreatePath()
. The first argument of the function is the starting point (I use the root part’s Position) and the second argument is an ending point (targetPosition parameter), both in terms ofVector3
. This function makes network calls, in other words, this function can fail from time to time due to network errors (or just backend issues that you can’t control), so in order to counter this, we will usepcall()
along with a retry system. If the function has retried a lot of times and it still fails, then we will not do anything but warn the error message in the output.
local function walkTo(targetPosition, yieldable)
local RETRY_NUM = 0
local success, errorMessage
repeat
RETRY_NUM += 1 -- add one retry
success, errorMessage = pcall(path.ComputeAsync, path, humanoidRootPart.Position, targetPosition)
if not success then -- if it fails, warn the message
warn("Pathfind compute path error: "..errorMessage)
task.wait(RETRY_COOLDOWN)
end
until success == true or RETRY_NUM > MAX_RETRIES
if success then -- if computing the path has no issues
else -- if retry chance is maxed out
warn("Pathfind compute retry maxed out, error: "..errorMessage)
return
end
end
About pcall()
Notice that I did not use the pcall
function like so:
pcall(function()
...
end)
This is because it creates another function to do another function, which is unnecessary work. So I just typed out like that to save some spaces. Also notice that I did not use :
when indexing the ComputeAsync
. :
is not suitable to be used here so we use .
, and then the second argument of that pcall
is the path
instance itself because
path.ComputeAsync(path, ...)
is the same thing as
path:ComputeAsync(...)
- If the function was successfully called with no issues, then we must check if the function was able to find a path between the two points. If it can compute a path, then
Path.Status
will beEnum.PathStatus.Success
. Hence, we can get the path’s waypoints, a table consisting of all points the humanoid must traverse through to get to the end goal, and cycle through each waypoint. We will create a variable which tells the humanoid to move to the corresponding waypoints. We will then usehumanoid:MoveTo()
to tell the humanoid to walk to that waypoint’s position, which can be accessed by getting the waypoint’s position property shown in the code below. If the waypoint requires the humanoid to jump, we will sethumanoid.Jump
to true.
if success then
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
local currentWaypointIndex = 2 -- not 1, because 1 is the waypoint of the starting position.
humanoid:MoveTo(waypoints[currentWaypointIndex].Position) -- move to the nth waypoint
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then -- if it requires the humanoid to jump
humanoid.Jump = true
end
else -- if the path can't be computed between two points, do nothing!
return
end
else -- this only runs IF the function has problems computing the path in its backend, NOT if a path can't be created between two points.
warn("Pathfind compute retry maxed out, error: "..errorMessage)
return
end
- Awesome! But we only told the humanoid to move one waypoint, so how do we cycle through the whole waypoints? If you think of using
for loops
, you are right! BUT, for loops are not suitable in this case. I will tell you why later. Instead, we will use an event calledhumanoid.MoveToFinished
. This basically gets fired every time the humanoid has reached a position called byhumanoid:MoveTo()
. In this case, this event gets fired every time it has reached a waypoint. When the event fires, we want to check if it has reached the waypoint in time (8 seconds) and has yet to reach the end goal, then we will tell the humanoid to move to the next waypoint.
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
local currentWaypointIndex = 2
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
end
end)
end
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
else
return
And we are done! For now. If your script has no issues and run the game, your humanoid would start walking to its assigned end goal.
Works smoothly, but there are some issues you have probably noticed besides the video if you pay attention closely.
Firstly in the video, the humanoid model walks to the end goal without playing any idle or running animation. This is because we don’t have a script that handles the animation of the script. To solve this issue, we can script a-
“WE DO NOT WANT TO SCRIPT A STUPID ANIMATION SCRIPT”
Okay, that’s fine! We have another method which is as shortcut. What we can do is we can “steal” the animation script inside of our character’s player model. All you need to do is run the game with your character, find your character model in Workspace, find a script called Animate, copy it, stop the game and paste it in workspace. You should notice there are also other things parented underneath it, all you need to do is delete the BindableEvent
named PlayEmote
, and then transfer the whole script into a server script since local scripts don’t work and you are done.
"too long im too lazy"
Sure, I have uploaded the scripts required to handle the animations. All you need to do is just select the script according to your rig type, place it under your humanoid model and let it do all the magic.
https://create.roblox.com/marketplace/asset/13450938683/Animate-R15
https://create.roblox.com/marketplace/asset/13450937287/Animate-R6
Onto the next issue, notice that our connection variable reachedConnection
never disconnects. This can cause unnecessary memory leaks when something is no longer needed. Memory leaks can cause your game to use up more memory which is unnecessary and jamming up the performance. So what do we do?
That’s right! We have to disconnect the event when the path has been cycled. To do this, we can add an else
statement inside the connected function where we tell the script to disconnect the event.
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
else
reachedConnection:Disconnect()
reachedConnection = nil -- you need to manually set this to nil! because calling disconnect function does not make the variable to be nil.
end
end)
Memory leaks is a wide and challenging topic to understand and overcome (at least for me), if you are interested in learning more, you can check out this awesome tutorial.
Furthermore, you will notice that our dummy model is unable to handle paths that are blocked, as shown in the video below:
This might not be an issue if your NPC is only moving from one location to another location for only one time and the path is inaccessible by other things, but, for the sake of this tutorial, we will combat all normal and common kinds of issues you have to deal with in PathfindingService
.
So how do we approach to this issue? Luckily, there’s an event called Path.Blocked
for every Path
object created through the service. This event, you guessed it, basically fires every time the path is being blocked by a physical part, so all we can do is, every time this event gets fired, we will tell the bot to stop cycling through its currently assigned path, compute a new one with the same arguments, and then cycle that newly created path.
But here’s the problem, the event does not know whether the waypoint of the path being blocked has been cycled through by the bot. Thankfully, the connected function of the event provides an argument we can use, that states the number of waypoint that is currently being blocked. For example, if there is a part blocking between the 5th and 6th waypoint, the event will fire and give the number 6 with extra dip, a number 7 as the argument in the connected function. To take advantage of this, we can check whether the current waypoint index is smaller than this number, and if it is, we will tell the bot to stop cycling through the current path and follow the newly computed one. Otherwise, we will do nothing as that waypoint has been cycled through.
- Create a new variable called
pathBlockedConnection
under the variablereachedConnection
.
local path = PathfindingService:CreatePath()
local reachedConnection
local pathBlockedConnection
- In the function right after setting up the
reachedConnection
connection, setpathBlockedConnection
to thePath.Blocked
event and connect it to a function. Make sure you create a parameter that represents the blocked waypoint index. Go ahead and set up the function with the applied description of it I just have just given.
pathBlockedConnection = path.Blocked:Connect(function(waypointNumber)
if waypointNumber > currentWaypointIndex then -- blocked path is ahead of the bot
reachedConnection:Disconnect() -- disconnect these events to prevent memory leaks
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
walkTo(workspace.EndGoal.Position, true) -- compute and cycle new path
end
end)
Since pathBlockedConnection
also contains an event like how reachedConnection
has as well, you have to disconnect it and set it to nil when it is no longer used, so apply this logic also when reachedConnection
is unnecessary.
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
else
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
end
Give it a test and it should be working!
One thing that I have totally forgotten is the yieldable
parameter in our function. We are going to need to implement a function for that.
- At the top of the script where the constant variables lives, creating a new constant variable named
YIELDING
, this will contain a boolean value that tells us if the function will yield. This must be in the main scope of the script and not the local scope as re-computations of new paths when the old path is blocked needs to refer it.
-- CONSTANTS --
local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local YIELDING = false
- In the main function after the two setups of the connections, we will use a
repeat until
loop to yield the main thread when the function needs to yield, untilYIELDING
becomes true.
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
if yieldable then
YIELDING = true
repeat
task.wait()
until YIELDING == false
end
else
- We have to set
YIELDING
to false at some part in our script so that it can stop yielding and move onto the next parts of the script, so we have to do that when the path has been fully cycled through.
else
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
YIELDING = false
end
And that’s actually it for the basic usage of Pathfinding
service. Here’s the full code.
Full Code
-- SERVICES --
local PathfindingService = game:GetService("PathfindingService")
-- CONSTANTS --
local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local YIELDING = false
local model = script.Parent
local humanoid = model.Humanoid
local humanoidRootPart = model.HumanoidRootPart
local path = PathfindingService:CreatePath()
local reachedConnection
local pathBlockedConnection
local function walkTo(targetPosition, yieldable)
local RETRY_NUM = 0
local success, errorMessage
repeat
RETRY_NUM += 1
success, errorMessage = pcall(path.ComputeAsync, path, humanoidRootPart.Position, targetPosition)
if not success then
warn("Pathfind compute path error: "..errorMessage)
task.wait(RETRY_COOLDOWN)
end
until success == true or RETRY_NUM > MAX_RETRIES
if success then
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
local currentWaypointIndex = 2
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
else
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
YIELDING = false
end
end)
end
pathBlockedConnection = path.Blocked:Connect(function(waypointNumber)
if waypointNumber > currentWaypointIndex then
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
walkTo(workspace.EndGoal.Position, true)
end
end)
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
if yieldable then
YIELDING = true
repeat
task.wait()
until YIELDING == false
end
else
return
end
else
warn("Pathfind compute retry maxed out, error: "..errorMessage)
return
end
end
walkTo(workspace.EndGoal.Position, true)
Agent Parameters and Pathfinding Modifiers
Through PathfindingService
, we are also exposed to some features that allow us to customize our path to make it look more “smarter”. At its most basic form, Pathfinding
service only computes a path with the shortest distance as much as possible. It ignores whether or not if the path that is computed is suited for your expectations and preferences.
But consider this:
If we were to use our script and make our character model to walk to the red part, it would walk in a straight line like so.
But what if we wanted our character to move along the blue lines?
This is where PathfindingModifier
s come in! They are modifiers that allows PathfindingService
to compute paths that suits our preferences. Not only that, we can also adjust some other properties that affects the final path as well.
To understand what they are, meet the AgentParameters
dictionary. If you look at the developer hub, you’ll find information about it, but I will explain all of its properties simply here:
- AgentRadius key determines the radius of our character. This is useful if we want to keep up a minimum separation distance between our character and its surrounding obstacles. Default value is 2, which is the default radius value of all rigs in Roblox. Anything higher than the default value will make the character have a minimum separation distance between obstacles with this formula:
Separation Distance = Value - 2 studs
Default value = 2
AgentRadius = 3
-
AgentHeight key is self explanatory. The default value of 5 is the default height for all rigs in Roblox.
-
AgentCanJump key is also self explanatory. Determines whether or not our character can jump. Default value is true.
-
AgentCanClimb key determines whether or not the humanoid can climb (only using Roblox’s truss part, you have to program your own climbing logic and use
PathfindingLinks
if you are not using the truss part!) . Default value is false. -
WaypointSpacing key determines the minimum amount of spacing between waypoints in a straight line(waypoints around a corner does not count). Default value is 4. If set to
math.huge
, it will create the least amount of waypoints whilst still providing the shortest path as much as possible. NOTE that changing this value does not respect thereached
parameter/argument mentioned in theHumanoid.MoveToFinished
connection. For instance, if you make each waypoints too far away from each other in a path, the value will returnfalse
even when your humanoid reaches the waypoint in more than 8 seconds.
WaypointSpacing = 8
WaypointSpacing = math.huge
i am unsure if this will increase performance soo lol
- Costs key contains a dictionary for our
PathfindingModifiers
.
Feel free to mess around with the other keys, since they are easy to learn. This part of the tutorial will mainly focus on the PathfindingModifiers
object in the Costs
key.
When I first learnt about this, I was puzzled. Thankfully, @Hexcede managed to brighten me up!
So what do these things do? These things are also known as “multipliers”, meaning that if I set a higher number in this multiplier, it will make the path “harder” to traverse/walk through it. For instance, imagine a path that has a distance of 100 studs before reaching a goal. If I set a cost of multiplier of 5, this will make it so that this path is 5x harder to traverse. It sounds confusing, but the point here is that it stores a multiplier value that decides on how hard is to get to the goal through this goal.
You might be asking what is this multiplier value multiplying with? Well, we don’t have a clear answer but according to @Hexcede, costs are somewhat directly proportional to distance traveled. This means, the higher the cost, the harder it is for the humanoid to traverse that path. For example, travelling on 30-studs-long path with a cost value of 10 will cost around 300 to travel on, on the other hand a 100-studs-long path with a cost value of 2 will cost 200 to travel on. In conclusion, PathfindingService
would prefer the 100-studs-long path because it costs less than 100 by the 30-studs-long path. This allow us to make our humanoids walk on longer paths with a low cost value over short paths with a high cost of values. Imagine a situation where you wanna force a humanoid to walk on the longer path when it has to choose which path to go, each with different lengths in studs. With the Costs parameter, we can make it so that the shorter path costs more than the longer path to travel, which will make pathfinding service to choose the longer path instead.
How is this useful you may ask? Well, just like the problem I have shown earlier, we can make it so that pathfinding service will choose to walk on the Cyan paths by lowering the cost to travel on that part. How do we do that? We can either make it so that the cost of traversing to the goal through the corroded metal part is higher than the cost of the Cyan parts or lower the cost of traversing on the Cyan parts. The way on how we lower the cost is by simply giving it a multiplier value of less than 1, because any number that is multiplied with less than 1 will output a lower number than its original. For instance, 500 x 0.5 = 250
.
The addition of this is also as simple as how we implement our method to counter blocked paths. It just needs one variable and that’s it! At the beginning of the script, we create a variable named AgentParameters
which contains a dictionary to customize our computed path. Inside it, we will add the Costs
key which contains another dictionary to store all the costs to traverse on certain parts/materials. In our case, the Cyan part has a metal material, so we would have to name the key as Metal exactly, then its value will be the multiplier cost. I am going to set this lower than 1 so that pathfinding service would likely choose that material to be appeared in our final path more often.
local Path:Path
local AgentParameters = {
WaypointSpacing = 4,
Costs = {
Metal = 0.1 -- if your material is other than Metal, name that material as the key exactly.
-- set it to lower than 1 to make the service more likely to include that material in the path
}
}
Path = PathfindingService:CreatePath(AgentParameters) -- the function receives an optional parameter which contains our AgentParameter dictionary in order to customize our path.
In the Costs
dictionary, each key inside it can either be the name of a material, terrain material of a unique ID for a PathfindingModifier
object which I will demonstrate later.
And that’s actually it! No catches, no strings no nothing. Let’s compare it with a path without the costs.
Without the dictionary (all costs are the same)
With the dictionary (Metal material cost is 0.1)
Of course, as I’ve said it is not limited to just BasePart materials, it can also be a terrain material or the PathfindingModifier
object!
Terrain material
Costs:
- Grass = 10
- Brick = 1
PathfindingModifier(can be parented under a BasePart)
the big corroded metal part contains the pathfinding modifier object
Use the Label property as the name of the key.
local AgentParameters = {
WaypointSpacing = 4,
Costs = {
AvoidThis = math.huge -- this will make it so that this part will never be included in the path even though it is the only way to get to the goal
}
}
The PathfindingModifier
object is useful if you want to define a certain region in your game as either traversable or not. To do this, you can define the region using a part, make sure its CanCollide property is false and make it transparent. To define the region as non-traversable, you can set its cost to a higher value according to the Label property of its PathfindingModifier
object. Otherwise, check the PassThrough property and the service will mark the region as traversable. You do not have to set its cost in this case. This is extremely useful if you are making a bot to walk through a hinged door in your game.
There is also a PathfindingLink
object, which is useful if you wanna trigger a custom event through attachments when your humanoid object is heading to a certain waypoint in the path. This is also useful if you want to make the humanoid traverse to waypoints that are impossible to get to without a special function that makes it possible to traverse. You can check out the developer hub as it has already explains it well, but I will just do one example.
Let’s say I want this humanoid to jump over the gap to get to the red block:
If we use PathfindingService
to compute the path, it fails because it is unable to find a clear path for the humanoid to traverse through. BUT, if we use PathfindingLink
, this is possible!
-
First, add two attachments and parent them under 2 parts. In this case, I will put one attachment each in the 2 big parts in the photo I have just shown.
-
Then, position both attachments to your desired location. As long as it is logical, it will work. In my case, I have to place it close but not touching to each other like so:
This really depends on where you want your humanoid to utilise these special waypoints, so you have to experiment this a lot to find your sweet spot.
-
Now, add a
PathfindingLink
object. Give it a name. I’ll call mineJumpGap
-
In the properties panel, assign
Attachment0
andAttachment1
to those attachments. The order does not matter .Name the label as the name of yourPathfindingLink
object.
forgot to name my Label
Also just in case if you are wondering, IsBidirectional
is the reason why the order of the attachments does not matter. If it is false, then Attachment0
is the starting point and Attachment1
is the ending point.
- Back inside the script, let’s create a
AgentParameters
dictionary, where inside theCost
dictionary, we will add our label with its respective value. I’ll give it a 2 so that it prefers walking instead of jumping assuming the gap is connected with a part.
-- CONSTANTS --
local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local YIELDING = false
local AGENT_PARAMETERS = {
AgentCanClimb = true, -- this does not matter. i just want my humanoid to jump.
Costs = {
JumpGap = 2
}
}
- To make this easier for you to add and update new
PathfindingLink
objects you may add in the future, we will create a dictionary which stores a key equivalent to a label’s name, and its respective value which is a function that stimulates the logic of that label. In our case, we want the humanoid to jump when it has reached that special waypoint (created by the first attachment), and then move to its connected waypoint (the second attachment).
-- SPECIAL WAYPOINTS FUNCTIONS --
local SPECIAL_WAYPOINTS = {
JumpGap = function(model, waypoints, currentWaypointIndex) -- every other logic functions may need to use these arguments for their own logic.
local humanoid = model:FindFirstChildWhichIsA("Humanoid")
if humanoid then
humanoid.Jump = true
humanoid:MoveTo(waypoints[currentWaypointIndex + 1].Position)
end
end,
}
-- remember, key = label, value = its logic function.
- In the
reachedConnection
variable which stores thehumanoid.MoveToFinished
event with its connected function, after we add one tocurrentWaypointIndex
, we will need to check if this next waypoint’s label (PathWaypoint.Label
) can be found in theSPECIAL_WAYPOINTS
table, and if such value exists, we will call that function, simple as that! Otherwise, we just tell it to move to the next waypoint.
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
if SPECIAL_WAYPOINTS[waypoints[currentWaypointIndex].Label] ~= nil then
SPECIAL_WAYPOINTS[waypoints[currentWaypointIndex].Label](model, waypoints, currentWaypointIndex)
else
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
end
And you are basically done!
This pretty much concludes how you can modify paths for your own liking using some special features offered by PathfindingService`.
Bot AI Chasing
I’m sure all of you have at least play a game where you have to escape from a threat before it gets you, take the game Piggy for example. In the game, a bot would spawn and pathfind its way to chase the closest player near to itself and when it touched the player, the player dies.
How does this work using pathfinding script? If you’ve dissected a bot AI chasing model, you’ll notice that most of them contains a function that gets the closest player’s character’s torso, and then pathfind it by only making the humanoid walking to either the second or third waypoint of the path. Why is that?
Based on my experience, I am sure it is something to do with whatever the bot is trying to get to. The script that I have taught you guys just now isn’t suitable to do such things. Here are a number of reasons:
- The script itself generally is only used for bots that is going to pathfind a location that’s fixed. If you are going to make a pathfind script for a bot that’s going to constantly chase a player that its position is dynamically changed every few seconds, it’s better if we just don’t make the bot walk through the whole series of the path, instead we want it to only walk to either the second or third waypoint of the path. With this, the bot doesn’t have to wait until it finishes looping through the whole path and start going to another position after that.
- The script can be severely deoptimized if not used right. The main reason why my previous pathfinding script in my last Pathfinding Service usage tutorial sucks and deoptimized is because of how frequently it is called per second. Previously, I would use a repetitive loop that spawns around 60 threads per second using
RunService.Heartbeat
event. The result? The script ended up calling that same function around 60 times per second and that is not good. To add the salt on the wound, there is afor loop
in the function which would be ran around 60 times per second or the same rate as how many times the function was called per second. This ended up making our bot look buggy and stuttering. So how do we solve this? Simple, with the logic I’ve explained in the previous reason, we will need a loop that waits for the humanoid to stop walking to the waypoint before executing the same function over and over again.while task.wait() do
loop is already good enough as it doesn’t spawns multiple threads per second and it yields if any code inside it has a yielding function. - The events (for handling blocked path and waypoint reached) connected inside the script would just add more performance drop in our script, but without it how can we solve such problems? Well, the first reason I’ve stated is already enough to counter this problem. Since the position of the player’s character model is constantly changing, we would compute a new path every time the while loop has done executing the function (when the yield is finished), and when we compute a new path, we would have a new series of waypoints with different positions. By computing a new path everytime our bot has reached to the second/third waypoint of the previous path, the computation of the path would avoid any obstacles and go around it to make our bot get to us without any issues.
Now that I’ve explained the reasons why, it’s time to create a new script. The flow of the script is simple. Everytime the while loop runs, it will call a function which tells the script to detect any closest player to the bot itself. If it managed to find one, we will tell the script to compute the path to it, and make our bot to only loop/walk through the second or third waypoint of the path before ending the function. Then this whole sequence would be repeated until our bot touches the player and makes the player die. Simple enough.
This part of the tutorial will be out soon.
I hope this tutorial benefits you. If there is any questions, feel free to leave a comment down…