After I fixed my pathfinding issue, I wanted to make all of my NPCs move in a line together. For example, if the player is in one part of the map, the NPCs form a line formation and march to the player
So far, the pathfinding is buggy, inefficient, and costly. I want to know if there is a better way to do this.
local PS = game:GetService("PathfindingService")
local ENEMY_FOLDER = workspace:WaitForChild("Bots")
local DESTINATION = Vector3.new(50, 0, 110)
local AGENT_RADIUS = 2
local FORMATION_RADIUS = 5
local WAYPOINT_REACH_DISTANCE = 2
local IDLE_ANIMATION_ID = "rbxassetid://109580015110725"
local WALK_ANIMATION_ID = "rbxassetid://108528753842263"
local function createDebugHitbox(size, position)
local debugPart = Instance.new("Part")
debugPart.Size = size
debugPart.Position = position
debugPart.Transparency = 0.6
debugPart.Anchored = true
debugPart.CanCollide = false
debugPart.BrickColor = BrickColor.new("Bright red")
debugPart.Parent = workspace
task.delay(2, function()
if debugPart then debugPart:Destroy() end
end)
end
local function loadAnimation(npc, animationId)
local humanoid = npc:FindFirstChild("Humanoid")
if humanoid then
local animation = Instance.new("Animation")
animation.AnimationId = animationId
local animator = humanoid:FindFirstChild("Animator") or humanoid:WaitForChild("Animator")
return animator:LoadAnimation(animation)
end
return nil
end
local function playAnimation(animationTrack, shouldLoop)
if animationTrack then
animationTrack.Looped = shouldLoop
animationTrack:Play()
end
end
local function stopAnimation(animationTrack)
if animationTrack then
animationTrack:Stop()
end
end
local function calculateFormationCenter(npcs)
local totalPosition = Vector3.new(0, 0, 0)
for _, npc in pairs(npcs) do
totalPosition = totalPosition + npc.PrimaryPart.Position
end
return totalPosition / #npcs
end
local function calculateRelativePositions(npcs, center)
local relativePositions = {}
for _, npc in pairs(npcs) do
relativePositions[npc] = npc.PrimaryPart.Position - center
end
return relativePositions
end
local function generatePath(startPos, endPos)
local path = PS:CreatePath({
AgentRadius = AGENT_RADIUS,
AgentHeight = 5,
AgentCanJump = true,
AgentJumpHeight = 5,
AgentMaxSlope = 45
})
path:ComputeAsync(startPos, endPos)
return path
end
local function moveNPC(npc, targetPosition, idleTrack, walkTrack)
local humanoid = npc:FindFirstChild("Humanoid")
if humanoid then
playAnimation(walkTrack, true)
stopAnimation(idleTrack)
humanoid:MoveTo(targetPosition)
createDebugHitbox(Vector3.new(2, 2, 2), targetPosition)
local reached = humanoid.MoveToFinished:Wait()
playAnimation(idleTrack, true)
stopAnimation(walkTrack)
return reached
end
end
local function followFormationPath(npcs, relativePositions, path, animationTracks)
local waypoints = path:GetWaypoints()
for _, waypoint in ipairs(waypoints) do
local center = waypoint.Position
local tasks = {}
for npc, relativePosition in pairs(relativePositions) do
local targetPosition = center + relativePosition
local idleTrack = animationTracks[npc]["Idle"]
local walkTrack = animationTracks[npc]["Walk"]
table.insert(tasks, coroutine.create(function()
moveNPC(npc, targetPosition, idleTrack, walkTrack)
end))
end
for _, task in ipairs(tasks) do
coroutine.resume(task)
end
task.wait(0.5)
end
end
local function handleBots()
local npcs = {}
local animationTracks = {}
for _, npc in pairs(ENEMY_FOLDER:GetChildren()) do
if npc:IsA("Model") and npc:FindFirstChild("Humanoid") and npc:FindFirstChild("HumanoidRootPart") then
npc.PrimaryPart = npc:FindFirstChild("HumanoidRootPart")
table.insert(npcs, npc)
animationTracks[npc] = {
Idle = loadAnimation(npc, IDLE_ANIMATION_ID),
Walk = loadAnimation(npc, WALK_ANIMATION_ID)
}
end
end
if #npcs == 0 then
warn("No NPCs found in the 'Bots' folder.")
return
end
while true do
local formationCenter = calculateFormationCenter(npcs)
local relativePositions = calculateRelativePositions(npcs, formationCenter)
local path = generatePath(formationCenter, DESTINATION)
if path.Status == Enum.PathStatus.Success then
followFormationPath(npcs, relativePositions, path, animationTracks)
else
warn("Pathfinding failed.")
end
task.wait(2)
end
end
handleBots()