Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
1

Could someone explain some of this Pathfinding code?

Asked by
Xl5Xl5 65
9 years ago

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!

1 answer

Log in to vote
6
Answered by
BlackJPI 2658 Snack Break Moderation Voter Community Moderator
9 years ago

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 Statusto 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
0
Woah! Thank you so much! I will keep reading this! :D Xl5Xl5 65 — 9y
Ad

Answer this question