I made a simple civilian NPC where they use pathfinding to navigate through the city, but its either the animation is smooth, but they stop before completing the whole path, or they complete the whole path as intended, but the animation is bugging.
Before you say it, yes, I set the NetworkOwner to nil
Follows path but not smooth
--// Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PathfindingService = game:GetService("PathfindingService")
--// Variables
local BinFolder = ReplicatedStorage:WaitForChild("Bin")
local NPCSource = BinFolder:WaitForChild("NPC")
local AnimateSource = BinFolder:WaitForChild("Animate")
local PathsFolder = workspace:WaitForChild("Waypoints"):WaitForChild("Outside")
local NPCSFolder = workspace:FindFirstChild("NPCS")
if not NPCSFolder then
NPCSFolder = Instance.new("Folder", workspace)
NPCSFolder.Name = "NPCS"
end
local LAST_PATH = nil
--// Settings
local NPCS_CAP = 20
local SPAWN_MIN = 1
local SPAWN_MAX = 5
--// Functions
local function getOrderedWaypoints(PathFolder: Folder)
local waypoints = {}
for _, path in ipairs(PathFolder:GetChildren()) do
if path:IsA("BasePart") and tonumber(path.Name) then
table.insert(waypoints, path)
end
end
table.sort(waypoints, function(a, b)
return tonumber(a.Name) < tonumber(b.Name)
end)
return waypoints
end
local function getRandomPathFolder()
local folders = {}
for _, child in ipairs(PathsFolder:GetChildren()) do
if child:IsA("Folder") then
table.insert(folders, child)
end
end
if #folders == 0 then return nil end
if #folders == 1 then
return folders[1]
end
local chosen
repeat
chosen = folders[math.random(1, #folders)]
until chosen ~= LAST_PATH
LAST_PATH = chosen
return chosen
end
local function moveNPCAlongWaypoints(npc, waypoints)
local humanoid = npc:FindFirstChildWhichIsA("Humanoid")
local npcPrimary = npc.PrimaryPart
if not humanoid or not npcPrimary then return end
for i, waypointPart in ipairs(waypoints) do
if not waypointPart or not waypointPart:IsA("BasePart") then
warn(("Invalid waypoint %d for NPC %s"):format(i, tostring(npc.Name)))
else
local destination = waypointPart.Position
if (npcPrimary.Position - destination).Magnitude <= 1.5 then
task.wait(0.1)
else
local path = PathfindingService:CreatePath({
AgentRadius = 2.5,
AgentHeight = 6,
AgentCanJump = true,
})
local success, err = pcall(function()
path:ComputeAsync(npcPrimary.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
local points = path:GetWaypoints()
for _, point in ipairs(points) do
humanoid:MoveTo(point.Position)
humanoid.MoveToFinished:Wait()
end
else
warn(("ComputeAsync error for NPC %s at waypoint %d: %s"):format(npc.Name, i, tostring(err)))
end
end
end
task.wait()
end
task.wait(1)
if npc and npc.Parent then npc:Destroy() end
end
local function SpawnCivilian()
local npcList = NPCSource:GetChildren()
if #npcList == 0 then return end
local npc = npcList[math.random(1, #npcList)]:Clone()
npc.Parent = NPCSFolder
npc.PrimaryPart:SetNetworkOwner(nil)
local RandomPathFolder = getRandomPathFolder()
if not RandomPathFolder then
npc:Destroy()
return
end
local waypoints = getOrderedWaypoints(RandomPathFolder)
if #waypoints == 0 then
npc:Destroy()
return
end
if math.random() < 0.5 then
local reversed = {}
for i = #waypoints, 1, -1 do
table.insert(reversed, waypoints[i])
end
waypoints = reversed
end
local humanoidRoot = npc:FindFirstChild("HumanoidRootPart") or npc.PrimaryPart
if humanoidRoot then
humanoidRoot.CFrame = waypoints[1].CFrame + Vector3.new(0, 3, 0)
end
table.remove(waypoints, 1)
task.spawn(function()
moveNPCAlongWaypoints(npc, waypoints)
end)
end
--// Initialize
while true do
if #NPCSFolder:GetChildren() < NPCS_CAP then
SpawnCivilian()
end
task.wait(math.random() * (SPAWN_MAX - SPAWN_MIN) + SPAWN_MIN)
end
Smooth but does not follow the whole path
--// Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PathfindingService = game:GetService("PathfindingService")
--// Variables
local BinFolder = ReplicatedStorage:WaitForChild("Bin")
local NPCSource = BinFolder:WaitForChild("NPC")
local AnimateSource = BinFolder:WaitForChild("Animate")
local PathsFolder = workspace:WaitForChild("Waypoints"):WaitForChild("Outside")
local NPCSFolder = workspace:FindFirstChild("NPCS")
if not NPCSFolder then
NPCSFolder = Instance.new("Folder", workspace)
NPCSFolder.Name = "NPCS"
end
local LAST_PATH = nil
--// Settings
local NPCS_CAP = 20
local SPAWN_INTERVAL = 1
--// Functions
local function getOrderedWaypoints(PathFolder: Folder)
local waypoints = {}
for _, path in ipairs(PathFolder:GetChildren()) do
if path:IsA("BasePart") and tonumber(path.Name) then
table.insert(waypoints, path)
end
end
table.sort(waypoints, function(a, b)
return tonumber(a.Name) < tonumber(b.Name)
end)
return waypoints
end
local function getRandomPathFolder()
local folders = {}
for _, child in ipairs(PathsFolder:GetChildren()) do
if child:IsA("Folder") then
table.insert(folders, child)
end
end
if #folders == 0 then return nil end
if #folders == 1 then
return folders[1]
end
local chosen
repeat
chosen = folders[math.random(1, #folders)]
until chosen ~= LAST_PATH
LAST_PATH = chosen
return chosen
end
local function moveNPCAlongWaypoints(npc, waypoints)
local humanoid = npc:FindFirstChildWhichIsA("Humanoid")
local npcPrimary = npc.PrimaryPart
if not humanoid or not npcPrimary then return end
local WAYPOINT_INDEX = 1
local REACHED_CONNECTION
local function moveToNext()
if WAYPOINT_INDEX > #waypoints then
if REACHED_CONNECTION then REACHED_CONNECTION:Disconnect() end
task.wait(1)
if npc then npc:Destroy() end
return
end
local path = PathfindingService:CreatePath({
AgentRadius = 2.5,
AgentHeight = 6,
AgentCanJump = true,
})
local destination = waypoints[WAYPOINT_INDEX].Position
local success, err = pcall(function()
path:ComputeAsync(npcPrimary.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
local points = path:GetWaypoints()
if not REACHED_CONNECTION then
REACHED_CONNECTION = humanoid.MoveToFinished:Connect(function(reached)
if reached then
WAYPOINT_INDEX += 1
moveToNext()
end
end)
end
WAYPOINT_INDEX = 1
for _, point in ipairs(points) do
humanoid:MoveTo(point.Position)
end
else
warn(("Path not computed for NPC %s at waypoint %d"):format(npc.Name, WAYPOINT_INDEX))
end
end
moveToNext()
end
local function SpawnCivilian()
local npcList = NPCSource:GetChildren()
if #npcList == 0 then return end
local npc = npcList[math.random(1, #npcList)]:Clone()
npc.Parent = NPCSFolder
npc.PrimaryPart:SetNetworkOwner(nil)
local RandomPathFolder = getRandomPathFolder()
if not RandomPathFolder then
npc:Destroy()
return
end
local waypoints = getOrderedWaypoints(RandomPathFolder)
if #waypoints == 0 then
npc:Destroy()
return
end
if math.random() < 0.5 then
local reversed = {}
for i = #waypoints, 1, -1 do
table.insert(reversed, waypoints[i])
end
waypoints = reversed
end
local humanoidRoot = npc:FindFirstChild("HumanoidRootPart") or npc.PrimaryPart
if humanoidRoot then
humanoidRoot.CFrame = waypoints[1].CFrame + Vector3.new(0, 3, 0)
end
table.remove(waypoints, 1)
task.spawn(function()
moveNPCAlongWaypoints(npc, waypoints)
end)
end
--// Initialize
while true do
if #NPCSFolder:GetChildren() < NPCS_CAP then
SpawnCivilian()
end
task.wait(SPAWN_INTERVAL)
end
what I meant by “not smooth”
heres what the “smooth but broken” code looks like
(the path are numbered from 1 to the last possible number)