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.
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
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.
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.
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
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
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
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
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)
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)
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
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.
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.