NPCs keep running into each other

So I made a script using PathfindingService. I have 6 rigs that do this. Here is my script:

local PathfindingService = game:GetService("PathfindingService")
 
-- Variables
local deliverer = script.Parent
local humanoid = deliverer.Humanoid
local destination = script.Parent.Parent.CashGiver
local returnDestination = script.Parent.Parent.DeliveryReturn

local isOnReturnPath = false

local box = game.ServerStorage.Box
local newBox = box:Clone()
newBox.Parent = deliverer

local animation = Instance.new("Animation")
animation.AnimationId = "http://www.roblox.com/asset/?id=4837921759"

local animationTrack = humanoid:LoadAnimation(animation)
animationTrack:Play()
 
-- Loop
-- Variables to store waypoints table and deliverer's current waypoint
local waypoints
local currentWaypointIndex

local path = PathfindingService:CreatePath()

-- Destination functions
 
local function followPath(destinationObject)
	-- Compute and check the path
	path:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints = {}
 
	if path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints = path:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex = 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
		print("Success")
	elseif path.Status == Enum.PathStatus.NoPath then
		-- Error (path not found); stop humanoid
		warn("Path not found, stopping NPC")
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	else
		error("Somehow the script made its way here")
	end
end
 
local function onWaypointReached(reached)
	print("Reached: " .. tostring(reached) .. ", Waypoint Index: " .. currentWaypointIndex)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	elseif not reached and isOnReturnPath then
		--NPC is on return path
		--reached is false
		followPath(returnDestination)
	elseif not reached and not isOnReturnPath then
		--NPC is not on return path
		--reached is false
		followPath(destination)
	elseif reached and currentWaypointIndex == #waypoints then
		if not isOnReturnPath then
			isOnReturnPath = true
			followPath(returnDestination)
		elseif isOnReturnPath then
			isOnReturnPath = false
			followPath(destination)
		end
	end
end
 
local function onPathBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex then
		-- Call function to re-compute the path
		followPath(destination)
	end
end

--  Return functions

-- Connect 'Blocked' event to the 'onPathBlocked' function
path.Blocked:Connect(onPathBlocked)
 
-- Connect 'MoveToFinished' event to the 'onWaypointReached' function
humanoid.MoveToFinished:Connect(onWaypointReached)

followPath(destination)


So the problem is that the path is created, and then the rig will run into another rig, making it take a long time for the rigs to get back. How can I fix this?

Note: I won’t set CanCollide to false, so don’t say a solution could be setting CanCollide to false.

2 Likes

You could raycast ahead of path and pause for possible interference, but how you decide priority is difficult. People use micro expression and body language to determine relative priority and intent.
Just an idea :slight_smile:

Perhaps a post I made can help.

Jumping should help no?

For example:

if PathWaypoint.Action == Enum.PathWaypointAction.Jump then
	Humanoid.Jump = true
end

You might want to have a look at PhysicsService. You can use SetPartCollisionGroup on the rigs, which can make them walk into eachother.

1 Like

Just remember that I don’t want to do this, and this is not what I am looking for.

The CreatePath() function of PathfindingService takes 2 parameters: AgentRadius and AgentHeight. These tell the NPC to go around objects in the path by the specified AgentRadius. Try setting it.

local path = PathfindingService:CreatePath(7)

The path instance also has a Blocked event you can connect to when the path is blocked. If the NPCs all start moving after computing their paths, they won’t take into account the other NPCs in their paths. You can recompute the path when the Blocked event is fired (when an NPC is in the way).

Keep in mind, though, that your previous function is looping through all the waypoints of the previous path so if you want to recompute the path when the path is blocked, you’ll have to adjust your loop.

1 Like

I guess the blocked event works but the only problem is that I can’t find out how to make the NPC go back to the starting place.

You can simply do the same thing you did for making the NPC go to the destination; compute a path to the starting place. To avoid repeating your code, instead of multiple functions (I noticed those functions were very similar), you could create one function with a parameter for the destination.

So this is my current code:

local PathfindingService = game:GetService("PathfindingService")
 
-- Variables
local deliverer = script.Parent
local humanoid = deliverer.Humanoid
local destination = script.Parent.Parent.CashGiver
local returnDestination = script.Parent.Parent.DeliveryReturn

local box = game.ServerStorage.Box
local newBox = box:Clone()
newBox.Parent = deliverer

local animation = Instance.new("Animation")
animation.AnimationId = "http://www.roblox.com/asset/?id=4837921759"

local animationTrack = humanoid:LoadAnimation(animation)
animationTrack:Play()
 
-- Loop
-- Variables to store waypoints table and deliverer's current waypoint
local waypoints
local currentWaypointIndex

local path = PathfindingService:CreatePath()
local returnPath = PathfindingService:CreatePath()
 
local function followPath(destinationObject)
	-- Compute and check the path
	path:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints = {}
 
	if path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints = path:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex = 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	else
		-- Error (path not found); stop humanoid
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	end
end
 
local function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	end
end
 
local function onPathBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex then
		-- Call function to re-compute the path
		followPath(destination)
	end
end
 
-- Connect 'Blocked' event to the 'onPathBlocked' function
path.Blocked:Connect(onPathBlocked)
 
-- Connect 'MoveToFinished' event to the 'onWaypointReached' function
humanoid.MoveToFinished:Connect(onWaypointReached)
 
followPath(destination)

How do I find if the NPC finished the entire path?

In your onWayPointReached() function, you can check if the waypoint reached was the last one.

local function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
    else
        -- The last raypoint was reached! The NPC finished the entire path :)
	end
end
1 Like
local waypoints
local currentWaypointIndex

local waypoints2
local currentWaypointIndex2

local path = PathfindingService:CreatePath()
local returnPath = PathfindingService:CreatePath()

-- Destination functions
 
function followPath(destinationObject)
	-- Compute and check the path
	path:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints = {}
 
	if path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints = path:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex = 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	else
		-- Error (path not found); stop humanoid
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	end
end
 
function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	elseif reached and currentWaypointIndex >= #waypoints then
		followPathBack(returnDestination)
	end
end
 
function onPathBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex then
		-- Call function to re-compute the path
		followPath(destination)
	end
end

--  Return functions

function followPathBack(destinationObject)
	-- Compute and check the path
	returnPath:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints2 = {}
 
	if returnPath.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints2 = returnPath:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex2 = 1
		humanoid:MoveTo(waypoints2[currentWaypointIndex].Position)
	else
		-- Error (path not found); stop humanoid
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	end
end

function onWaypointReachedReturn(reached)
	if reached and currentWaypointIndex2 < #waypoints2 then
		currentWaypointIndex2 = currentWaypointIndex2 + 1
		humanoid:MoveTo(waypoints2[currentWaypointIndex2].Position)
	elseif reached and currentWaypointIndex2 >= #waypoints2 then
		followPath(destination)
	end
end

function onPathReturnBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex2 then
		-- Call function to re-compute the path
		followPathBack(returnDestination)
	end
end

-- Connect 'Blocked' event to the 'onPathBlocked' function
path.Blocked:Connect(onPathBlocked)
returnPath.Blocked:Connect(onPathReturnBlocked)
 
-- Connect 'MoveToFinished' event to the 'onWaypointReached' function
humanoid.MoveToFinished:Connect(onWaypointReached)
 
followPath(destination)

So the onWaypointReachedReturn function is never fired. How can I fix this? I can’t do

humanoid.MoveToFinished:Connect(onWaypointReachedReturn)

Since there is already an event with that…

Also the NPC always just stops on the return path (Probably because of what I said up there ^^)

Rather than having two different sets of functions, one for the original path, one for the return path, why not just call followPath() again with a different destination? Then it’ll recompute path and start on the path back. So instead, just call followPath() again with the new destination. Your onWayPointReachedReturn() is not firing because it’s not connected; onWaypointReached() is.

function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	elseif reached and currentWaypointIndex >= #waypoints then
		followPath(returnDestination)
        -- Nothing wrong with using the same function ;)
	end
end

However, keep in mind that if you wanted the deliverers to go back and forth forever, you need (and can simplify with) a boolean variable like isOnReturnPath that you set depending on what path the NPC is on.

-- Up above somewhere
local isOnReturnPath = false
-- When the path is finished in onWayPointReached()
if not isOnReturnPath then
    isOnReturnPath = true
    followPath(returnDestination)
else
    isOnReturnPath = false
    followPath(destination)
end
1 Like

you could differentiate between collision groups to fix this.
but you will also need to update the waypoints AS the player is moving in some cases. (like targeting/following or checking for a new movement).

if(moving) then
  spawn(function()
    while(moving) do
       --update waypoints list
       wait(delayTime)
    end
  end)
end

What is happening now with this script:

local PathfindingService = game:GetService("PathfindingService")
 
-- Variables
local deliverer = script.Parent
local humanoid = deliverer.Humanoid
local destination = script.Parent.Parent.CashGiver
local returnDestination = script.Parent.Parent.DeliveryReturn

local isOnReturnPath = false

local box = game.ServerStorage.Box
local newBox = box:Clone()
newBox.Parent = deliverer

local animation = Instance.new("Animation")
animation.AnimationId = "http://www.roblox.com/asset/?id=4837921759"

local animationTrack = humanoid:LoadAnimation(animation)
animationTrack:Play()
 
-- Loop
-- Variables to store waypoints table and deliverer's current waypoint
local waypoints
local currentWaypointIndex

local waypoints2
local currentWaypointIndex2

local path = PathfindingService:CreatePath()
local returnPath = PathfindingService:CreatePath()

-- Destination functions
 
function followPath(destinationObject)
	-- Compute and check the path
	path:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints = {}
 
	if path.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints = path:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex = 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	else
		-- Error (path not found); stop humanoid
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	end
end
 
function onWaypointReached(reached)
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
	elseif reached and currentWaypointIndex >= #waypoints then
		followPathBack(returnDestination)
		isOnReturnPath = true
	end
end
 
function onPathBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex then
		-- Call function to re-compute the path
		followPath(destination)
	end
end

--  Return functions

function followPathBack(destinationObject)
	-- Compute and check the path
	returnPath:ComputeAsync(deliverer.HumanoidRootPart.Position, destinationObject.PrimaryPart.Position)
	-- Empty waypoints table after each new path computation
	waypoints2 = {}
 
	if returnPath.Status == Enum.PathStatus.Success then
		-- Get the path waypoints and start deliverer walking
		waypoints2 = returnPath:GetWaypoints()
		-- Move to first waypoint
		currentWaypointIndex2 = 1
		humanoid:MoveTo(waypoints2[currentWaypointIndex].Position)
	else
		-- Error (path not found); stop humanoid
		warn("Could not find path, stopping NPC")
		humanoid:MoveTo(deliverer.HumanoidRootPart.Position)
	end
end

function onWaypointReachedReturn(reached)
	if reached and currentWaypointIndex2 < #waypoints2 then
		currentWaypointIndex2 = currentWaypointIndex2 + 1
		humanoid:MoveTo(waypoints2[currentWaypointIndex2].Position)
	elseif reached and currentWaypointIndex2 >= #waypoints2 then
		followPath(destination)
		isOnReturnPath = false
	end
end

function onPathReturnBlocked(blockedWaypointIndex)
	-- Check if the obstacle is further down the path
	if blockedWaypointIndex > currentWaypointIndex2 then
		-- Call function to re-compute the path
		followPathBack(returnDestination)
	end
end

-- Connect 'Blocked' event to the 'onPathBlocked' function
path.Blocked:Connect(onPathBlocked)
returnPath.Blocked:Connect(onPathReturnBlocked)
 
-- Connect 'MoveToFinished' event to the 'onWaypointReached' function
humanoid.MoveToFinished:Connect(function(reached)
	if isOnReturnPath then
		onWaypointReachedReturn(reached)
	elseif not isOnReturnPath then
		onWaypointReached(reached)
	end
end)
 
followPath(destination)

The NPC still

Also, if I get in the way of the NPC, it still just runs into me. I might try doing PathfindingService:CreatePath(10)

Edit: ^^^ made the NPC not move at all.

Personally, rather than recomputing paths when blocked by an NPC, I’d actually do a raycast on Heartbeat several studs in front of each rig. In your raycast you can determine if another NPC is in the way, and if so, turn in one direction or another until that NPC is no longer detected.

Another thing I’d suggest is using the unit vector returned by the raycast to determine a direction. For a flat wall, this would be pointed straight out from the wall, so if your NPC is traveling towards that wall, it’d make sense to turn towards that outwards facing unit vector. The way I’d probably do this is by lerping the wall’s unit vector and the NPC’s target unit vector based on the distance. At your minimum distance, e.g. four studs you could lerp the target unit fully to the raycast unit. Otherwise, if the NPC is further you could lerp to the actual target direction. You’d also want to math.clamp the alpha for the lerp to 0-1.

This could cause the NPC to swap between two different unit vectors each frame, however this would not be particularly noticable, and may just make the npc appear to wobble a little when an object blocks its path.

There’s no use in having isOnReturnPath if you’re not going to follow my advice before to halve your number of functions needed.

Notice that this block of code ought to execute when the NPC finishes the path, so it should go in onWayPointReached() when it detects that the path is finished.
As for why your NPC is stopping on the return path, that’s because, as I said earlier,

In your function followPathBack(), you create a new set of waypoints, waypoints2, but since onWayPointReachedReturn() is not connected, it uses onWayPointReached() instead, which uses the original set of waypoints, waypoints. The solution is not to connect onWayPointReachedReturn(); the solution is to get rid of the last three functions and simply use followPath() again, like I showed you in the code block I wrote (which you used incorrectly) :joy:

Edit: I saw the changes to your code and see what you tried to do with onWayPointReached(). Yes, this could work (though I’d need to read a little more to find the error) but I still recommend using less functions.

Why should you use less functions? There’s a programming principle called “Don’t Repeat Yourself” (DRY). You know it’s important because it has its own acronym :stuck_out_tongue:
To increase productivity and halve the amount of code you have to read, check through, and debug, the principle states “Don’t Repeat Yourself when it’s not necessary”. If you have functions that do really similar things except one value has changed, it’s easier to use a parameter for that value and pass it different values both times. In your case, followPath() and followPathBack() really are the exact same functions except they deal with different values. Same with the other two unnecessary functions you wrote. So instead of creating new variables, you can replace the variables that you used before for your previous path and use new values instead. :slightly_smiling_face:

1 Like

I think this tone is rather condescending. It’s not a great idea to assume people should understand what your code does. You could write the simplest thing in the world, and a lot of developers, even experienced ones are still likely to misinterpret what your code is doing, to different extents, but still, it happens.

Edit: I misinterpreted what you were saying, however I still find your tone a bit condescending.

Yes, you’re right; I admit to losing my temper a little when I saw my advice wasn’t followed when I should consider more that he may not have understood my advice. I tried to explain a little more what I expected him to do to reduce repetition in the code but I’ll try to explain further.

Hmm… I still don’t get it. I tried it with your code, and it worked just like last time. The NPC would randomly stop, and it kept running into the other NPCs.

I don’t know how to use raycasts yet, but I’ll see what I can learn.