Hey there! I was just wondering if someone can explain each part of this script? I am curious how it works.
Would be great if someone can explain it! Please help!
local p=game:GetService("PathfindingService") local nodes={} for _,v in pairs(workspace:GetChildren())do if v.Name=="Node"then nodes[#nodes+1]=v end end for _,v in pairs(workspace.NPC:GetChildren())do wait(.1) Spawn(function() while wait()do local target=nodes[math.random(1,#nodes)] local path=p:ComputeRawPathAsync(v.Torso.Position,target.Position,500) if path.Status==Enum.PathStatus.Success then local points=path:GetPointCoordinates() for _,point in pairs(points)do local part,P=workspace:FindPartOnRay(Ray.new(point,Vector3.new(0,-10,0))) if part then point=P points[_]=P end end local br=false local distance; for _=1,#points,2 do local point=points[_] if br then break end local count=0 repeat count=count+1 if count>100 then br=true break end v.Humanoid:MoveTo(points[math.min(#points,_+1)]) if point.Y>=v.Humanoid.Torso.Position.Y-1 then v.Humanoid.Jump=true end distance=(point*Vector3.new(1,0,1)-v.Torso.Position*Vector3.new(1,0,1)).magnitude wait() until distance<5 end else v.Humanoid:MoveTo(v.Torso.CFrame*Vector3.new(0,0,5)) end end end) end
Thanks for reading!
We'll start off by developing a simpler bit of code and then work towards this one.
Throughout this answer we will be using the Roblox PathfindingService. The wiki has a great tutorial for it here.
To start, we will create a program that will attempt to generate a path between two parts: a start node and a finish node. These 'nodes' will simply be parts (or and array of positions) that will tell us information on where the path should start at and be attempting to go.
But how to we create a path? There are two functions that will generate an array of positions between two of our nodes. There is ComputeRawPathAsync
(a little faster) and ComputeSmoothPathAsync
(a little slower). The raw path will be in more of a grid, where as the smooth path will try to curve the turns to make it a little smoother. Try both of them out to see the difference for your self!
local Pathfinding = game:GetService("PathfindingService") local nodes = { game.Workspace.StartNode, game.Workspace.FinishNode, } local path = Pathfinding:ComputeSmoothAsync(nodes[1], nodes[2], 500)
Now that we've got the path generated, we have to check to see if the path is complete or not. If the path can be completed it will set the property Status
to Enum.PathStatus.Success
. So we can do a quick little if statement to check if the path is good to use. Also, you might want to check out the other PathStatus enums as there is couple different statuses that could be useful.
if path.Status == Enum.PathStatus.Success then else warn("Path creation unsuccessful") end
Now we can do what ever we want with the path. For example, you could have an NPC follow the path but getting the points from the path using the GetPointCoordinates function:
local humanoid = game.Workspace.NPC.Humanoid local points = path:GetPointCoordinates() for _, point in ipairs (points) do humanoid:MoveTo(point) repeat local distance = (point - humanoid.Torso.Position).magnitude wait() until distance <= 3 end
Full Script (only runs once):
local Pathfinding = game:GetService("PathfindingService") local nodes = { game.Workspace.StartNode, game.Workspace.FinishNode, } local path = Pathfinding:ComputeSmoothAsync(nodes[1], nodes[2], 500) if path.Status == Enum.PathStatus.Success then local humanoid = game.Workspace.NPC.Humanoid local points = path:GetPointCoordinates() for _, point in ipairs (points) do humanoid:MoveTo(point) repeat local distance = (point - humanoid.Torso.Position).magnitude wait() until distance <= 3 end else warn("Path creation unsuccessful") end
What the script you posted does
The script you posted uses all of what I just taught you but instead generates a coroutine for every player which selects a random node in the world and makes the player move towards that node.
There are some other things in here that help to prevent certain glitches from happening, like the lines 17-23 which place the points on the ground so that they aren't floating and potentially causing errors, and line 47 where it moves the character 5 studs backwards incase it gets stuck.
What I am going to do is break up this script into sections and attempt to make it more readable and easier to understand.
Script Setup
This is pretty straight forward. I did however change spawn
to coroutine.wrap
so that you could catch errors on the thread. I have also made it so that all the NPCs must be stored in an object called "NPCs" in workspace and all the nodes must be stored in an object called "Nodes".
local Pathfinding = game:GetService("PathfindingService") local nodes = game.Workspace.Nodes:GetChildren() for _, NPC in pairs (game.Workspace.NPCs:GetChildren()) do local error = coroutine.wrap(function() -- All the code will be in here end) if error then error(error) end end
Setting Points on the ground:
This function works by casting a ray down 10 studs from each point in the path. If a part is hit, then it sets the position of the point to the hit position resulting in the new position sitting on the ground. This is useful for preventing the character from getting stuck in certain situations and for detecting when the player needs to jump.
local function SetPointsOnGround(path) local points = path:GetPointCoordinates() for key, point in pairs (points) do local part, position = game.Workspace:FindPartOnRay(Ray.new(point, Vector3.new(0, -10, 0))) if part then points[key] = position end end return points end
Moving Character:
This function simply loops through all the points and moves the player towards them. While the player is moving, a repeat loop checks to see how far away the player is from the point, if the player needs to jump, and if the player is stuck and should break out of the loop.
local function MoveCharacter(points, humanoid) local shouldBreak = false for _, point in ipairs (points) do if shouldBreak then break end humanoid:MoveTo(point) local count = 0 repeat local distance = (point - humanoid.Torso.Position).magnitude if point.Y > humanoid.Torso.Position.Y - 1 then humanoid.Jump = true end if count >= 50 then shouldBreak = true break end count = count + 1 wait() until distance <= 4 end end
While Loop:
This is the main portion. This is what creates the path and calls the function to make the humanoid move. If the player is in a spot where it can not generate a path, it will attempt to move backwards 5 studs.
while true do local target = nodes[math.random(1, #nodes)] local path = Pathfinding:ComputeSmoothPathAsync(NPC.Torso.Position, target.Position, 500) if path.Status == Enum.PathStatus.Success then local points = SetPointsOnGround(path) MoveCharacter(points, NPC.Humanoid) else NPC.Humanoid:MoveTo(NPC.Humanoid.CFrame*Vector3.new(0, 0, 5)) end wait() end
Full Script:
local Pathfinding = game:GetService("PathfindingService") local nodes = game.Workspace.Nodes:GetChildren() for _, NPC in pairs (game.Workspace.NPCs:GetChildren()) do local error = coroutine.wrap(function() local function SetPointsOnGround(path) local points = path:GetPointCoordinates() for key, point in pairs (points) do local part, position = game.Workspace:FindPartOnRay(Ray.new(point, Vector3.new(0, -10, 0))) if part then points[key] = position end end return points end local function MoveCharacter(points, humanoid) local shouldBreak = false for _, point in ipairs (points) do if shouldBreak then break end humanoid:MoveTo(point) local count = 0 repeat local distance = (point - humanoid.Torso.Position).magnitude if point.Y > humanoid.Torso.Position.Y - 1 then humanoid.Jump = true end if count >= 50 then shouldBreak = true break end count = count + 1 wait() until distance <= 4 end end while true do local target = nodes[math.random(1, #nodes)] local path = Pathfinding:ComputeSmoothPathAsync(NPC.Torso.Position, target.Position, 500) if path.Status == Enum.PathStatus.Success then local points = SetPointsOnGround(path) MoveCharacter(points, NPC.Humanoid) else NPC.Humanoid:MoveTo(NPC.Humanoid.CFrame*Vector3.new(0, 0, 5)) end wait() end end) if error then error(error) end end