I’m making a game that involves interaction with NPCs. and one of the NPC types is “Neutral”
all they are doing basically wandering around aimlessly and then reacting to the surrounding event
Now back to my problem. Normally I could just use MoveTo()
+ math.Random
to just make them wander around but with my fascination with pathfinding and from my experience it is not as good as using PathFindingService
Another problem is that I don’t want to copy and paste the script into every copy of the NPC I wish to have pathfinding because of the optimization issue
So I make sure to pack all NPC into a folder called “NPC” with each of them having Tags corresponding to their type (in this case “Neutral”)
With the help of the amazing Tutorial by notzeussz I manage to implement the code into something like a cheap version of OOP script but at the moment it only controls one NPC which is the reason I made this post
Summary of my goal
- single Server script (if possible) that control all the npc with Tag “Neutral”
- Each npc having random time they’ll start “Wandering” if they all start walking at the same time It will just look weird and too robotic to my liking
- Each npc having their own random path and not just go at the same point
The Script
local pfs = game:GetService("PathfindingService")
local cs = game:GetService("CollectionService")
local MAX_RETRIES = 5
local RETRY_COOLDOWN = 5
local YIELDING = false
local Agent = {
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = true,
AgentCanClimb = true,
Costs = {
Danger = math.huge
}
}
local model
local humanoid
local humanoidRootPart
local path = pfs:CreatePath(Agent)
local reachedConnection
local pathBlockedConnection
function GetNPC()
for _, npc in pairs(workspace.NPC:GetChildren()) do
if npc:WaitForChild("Humanoid") and npc.Humanoid.Health > 0 and npc:HasTag("Neutral") then
return npc
end
end
end
local function CreateRandomGoal()
return GetNPC():WaitForChild("HumanoidRootPart").Position + Vector3.new(math.random(-30,30), math.random(-3,3), math.random(-30,30))
end
function walkTo(targetPosition, yieldable)
local RETRY_NUM = 0
local success, errorMessage
humanoid = GetNPC():WaitForChild("Humanoid")
humanoidRootPart = GetNPC():WaitForChild("HumanoidRootPart")
repeat
RETRY_NUM += 1
success, errorMessage = pcall(path.ComputeAsync, path, humanoidRootPart.Position, targetPosition)
if not success then
warn("Pathfind compute path error: "..errorMessage)
task.wait(RETRY_COOLDOWN)
end
until success == true or RETRY_NUM > MAX_RETRIES
if success then
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
local currentWaypointIndex = 2
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and currentWaypointIndex < #waypoints then
currentWaypointIndex += 1
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
else
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
YIELDING = false
end
end)
end
pathBlockedConnection = path.Blocked:Connect(function(waypointNumber)
if waypointNumber > currentWaypointIndex then
reachedConnection:Disconnect()
pathBlockedConnection:Disconnect()
reachedConnection = nil
pathBlockedConnection = nil
walkTo(workspace.EndGoal.Position, true)
end
end)
humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
if waypoints[currentWaypointIndex].Action == Enum.PathWaypointAction.Jump then
humanoid.Jump = true
end
if yieldable then
YIELDING = true
repeat
task.wait()
until YIELDING == false
end
else
return
end
else
warn("Pathfind compute retry maxed out, error: "..errorMessage)
return
end
end
while task.wait(math.random(10,20)) do
local Goal = CreateRandomGoal()
walkTo(Goal, true)
end