PathfindingService is making NPC jump off the edge of map instead of chasing player

Hello devs!

You might of seen my posts on my piggy thing but I have switched to pathFinding, not Humanoid:MoveTo() (so my piggy jumps).

But instead of chasing the player + jumping when it needs to, it instead has different ideas.
The idea of dying.
Like, jumping off the edge of the map. It only has 1 waypoint which is when it starts moving. It does not chase the player.


Here is the ServerScript in the NPC:

local npcHRP = script.Parent.HumanoidRootPart

local PathFindingServ = game:GetService("PathfindingService")

local npcH = script.Parent.Humanoid
local npcT = script.Parent.Torso

local function GetNearestPlayer(minimumDistance)
    local closestMagnitude = minimumDistance or math.huge
    --minimumDistance is a number in studs
    local closestPlayer
    for i,v in next, game.Players:GetPlayers() do
		local Character = v.Character
		if (Character) then
			local humanoid = Character.Humanoid
			local HRP = Character.HumanoidRootPart
			if (humanoid.Health > 0) then
				local mag = (npcHRP.Position - HRP.Position).Magnitude
				if (mag <= closestMagnitude) then
					closestPlayer = v
					closestMagnitude = mag
				end
			end
		end
	end
	return closestPlayer
end

while wait() do
	local nearPlr = GetNearestPlayer(70)
	pcall(function()
		local path = PathFindingServ:CreatePath()
		path:ComputeAsync(npcHRP.Position, nearPlr.Character.HumanoidRootPart.Position)
		local waypoints = path:GetWaypoints()

		for _, wp in pairs(waypoints) do
			if wp.Action == Enum.PathWaypointAction.Jump then
				script.Parent.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping) -- If I put a brick in front of the NPC, it doesn't jump.
			end
			print(wp.Action)
			script.Parent.Humanoid:Move(wp.Position)
			script.Parent.Humanoid.MoveToFinished:Wait()
		end
	end)	
end

NOTE: If I put a brick in front of the NPC, it doesn’t jump.

1 Like

You shouldn’t do the pathfinding stuff in a pcall(). Only use pcall() for errors that you can’t control yourself, like datastore or HttpService. Using pcall() for random things mask errors, and seeing the errors are helpful in debugging and finding issues with your code.

Also, you only need to create one path. So you can remove it from the while wait() do and put it at the top of the script instead. Calling :ComputeAsync() will always automatically recompute the path, so it’s a waste to create a new path each time.

Also, once you run :ComputeAsync(), make sure path.Status == Enum.PathStatus.Success. If it doesn’t equal that, then re-compute the path so the NPC knows where to go. You also don’t have any kind of measure to handle when a path gets blocked, so I would recommend adding that. Listen to when the path gets blocked and recompute the path if the blocked waypoint wasn’t passed by the NPC.

The article on pathfinding on the developer docs (Character Pathfinding | Documentation - Roblox Creator Hub) will teach you how to do all of these things.

3 Likes

How would I make it so it keeps getting all near players though?
Would I use a variable?

1 Like

And I need to use pcall or it says the player’s character is nil which I think I can fix?

If the player’s character is nil, then that’s a problem. You should fix it instead of masking the error using pcall().

Every couple of seconds, find the closest player and make a path to that player. Then you can follow that path normally.

Ok, I will use a corutine for that so I can do

while wait(3) do
   variableName = GetNearestPlayer(70)
end

Wait but how would I make it keep making a path for the NPC to follow?

Is it another while wait(3) do?

Does this work?

local npcHRP = script.Parent.HumanoidRootPart

local PathFindingServ = game:GetService("PathfindingService")

local npcH = script.Parent.Humanoid
local npcT = script.Parent.Torso

local function GetNearestPlayer(minimumDistance)
    local closestMagnitude = minimumDistance or math.huge
    --minimumDistance is a number in studs
    local closestPlayer
    for i,v in next, game.Players:GetPlayers() do
		local Character = v.Character
		if (Character) then
			local humanoid = Character.Humanoid
			local HRP = Character.HumanoidRootPart
			if (humanoid.Health > 0) then
				local mag = (npcHRP.Position - HRP.Position).Magnitude
				if (mag <= closestMagnitude) then
					closestPlayer = v
					closestMagnitude = mag
				end
			end
		end
	end
	return closestPlayer
end

local closestPlayer = GetNearestPlayer(70)

local newThing = coroutine.create(function()
	while wait(3) do
		closestPlayer = GetNearestPlayer(70)
	end
end)

local newPath = coroutine.create(function(path)
	while wait(3) do
		path:ComputeAsync(npcT.Position, closestPlayer:WaitForChild("Character").HumanoidRootPart.Position)
	end
end)

coroutine.resume(newThing)

local path = PathFindingServ:CreatePath()

coroutine.resume(newPath(path))

local waypoints = path:GetWaypoints()
for _, wp in pairs(waypoints) do
	if wp.Action == Enum.PathWaypointAction.Jump then
		script.Parent.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
	end
	print(wp.Action)
	script.Parent.Humanoid:Move(wp.Position)
	script.Parent.Humanoid.MoveToFinished:Wait()
end

Nope it doesn’t.

line 47: attempt to call a thread value

local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath()

local function getClosestPlayer(position)
    local players = Players:GetPlayers()
    local closestDistance, closestPosition, closestPlayer = math.huge, nil, nil

    for i, player in ipairs(players) do
        if not player.Character then continue end

        local playerPosition = player.Character:GetPrimaryPartCFrame()
        local distance = (playerPosition.Position - position).Magnitude

        if distance < closestDistance then
            closestDistance = distance
            closestPosition = playerPosition.Position
            closestPlayer = player
        end
    end

    return closestPlayer, closestPosition
end

while wait(<arbitrary number here>) do
    local currentPosition = <NPCs Vector3 pos here>
    local closestPlayer, closestPosition = getClosestPlayercurrentPosition)
    
    path:ComputeAsync(currentPosition, closestPosition)
    if path.Status == Enum.PathStatus.Success then
        local waypoints = path:GetWaypoints()
        local currentWaypoint = 0

        path.Blocked:Connect(function(index)
            if index >= currentWaypoint then
                -- recompute path
            end
        end)
    else
        -- recompute path
    end
end
1 Like

How would I re-compute it? Would I just do createpath all over again?

btw you’re using coroutines incorrectly. The proper way is coroutine.resume(coro, ...args).

local thread = coroutine.create(function(...)
    return select("#", ...)
end)

local argCount = coroutine.resume(thread, 1, 2, 3)
print(argCount) --> 3
1 Like

No, if you just remove the local from in front of the function, you can call the function inside itself.

I am not an advanced scripter so I don’t understand what this means.

I litteraly don’t understand anything right now.

Here is the new script:

local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath()

function getClosestPlayer(position)
    local players = Players:GetPlayers()
    local closestDistance, closestPosition, closestPlayer = math.huge, nil, nil

    for i, player in ipairs(players) do
        if not player.Character then continue end

        local playerPosition = player.Character:GetPrimaryPartCFrame()
        local distance = (playerPosition.Position - position).Magnitude

        if distance < closestDistance then
            closestDistance = distance
            closestPosition = playerPosition.Position
            closestPlayer = player
        end
    end

    return closestPlayer, closestPosition
end

while wait(3) do
    local currentPosition = script.Parent.Torso.Position
    local closestPlayer, closestPosition = getClosestPlayer(currentPosition)
    
    path:ComputeAsync(currentPosition, closestPosition)
    if path.Status == Enum.PathStatus.Success then
        local waypoints = path:GetWaypoints()
        local currentWaypoint = 0

        path.Blocked:Connect(function(index)
            if index >= currentWaypoint then
				path:ComputeAsync(currentPosition, closestPosition)
            end
        end)
    else
		path:ComputeAsync(currentPosition, closestPosition)
    end
end

How would I change the distance?

It’s called recursion:

function thing()
    thing() --> you can call the function from inside itself
end

you have to be careful when you do it though because in the above code block it creates an infinite loop which will result in a stack overflow.

I give up on this. I don’t understand a single THING.