Hello! recently, ive tried coding my own enemy AI into my game. I was able to get the core functionality for individual members, but later realized I wanted to have these enemies move in groups.
example
what i want to do:
npc groups consisting of a leader and member(s), with the members following the leader’s position
problems currently arising:
- core movement sort of works, but the members only move after the leader reaches x waypoint, i hope to have the members move along the leader
- concerned about performance
- for some reason after some time my leader gets super slow with pathfinding and takes like 3x longer to reach waypoints than before
-- services --
local pfs = game:GetService("PathfindingService")
local collectionService = game:GetService("CollectionService")
local squadrons = collectionService:GetTagged("Squadron") -- list of all squadrons in workspace
local waypointFolder = workspace.Waypoints
local squadronSizes = {}
local NPCOffsets = {}
for _, squad in ipairs(squadrons) do -- adding the npc offsets into a list, will be useful in moving squad members(not leaders)
local squadronOffsets = {} --creating a seperate table for each of the squadrons holding member offsets
squadronSizes[squad] = squad:GetExtentsSize().X / 2
local leader
for _, child in ipairs(squad:GetChildren()) do --finding the leader of the squadron
if collectionService:HasTag(child, "Leader") then
leader = child
break
end
end
for i, member : Model in squad:GetChildren() do --loop through all members of all squadrons
if member == leader or not member:IsA("Model") then continue end
local leaderPos = leader.PrimaryPart.Position
local memberPos = member.PrimaryPart.Position
local offset = leaderPos - memberPos
table.insert(squadronOffsets, offset)
end
NPCOffsets[squad] = squadronOffsets
end
function GetRandomWaypoint(waypointId : string) --getting a random waypoint with given waypointID
for _, folder in ipairs(waypointFolder:GetChildren()) do
if folder:GetAttribute("WaypointId") ~= waypointId then continue end -- in the waypoint folder, there should be various other folders, each with their own tag to represent what group of waypoints they hold, i.e. kitchen or hallway
local waypoints = folder:GetChildren()
local waypoint = waypoints[math.random(1, #waypoints)]
return waypoint.Position
end
end
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local modifier = Instance.new("PathfindingModifier")
modifier.Label = "Player"
modifier.PassThrough = true
modifier.Parent = character
end)
end)
function MoveLeader(squad : Model, leader : Model, destination : Vector3) --designated script for moving the leader, not necessary for other members as their position is based off leaders position
local hum : Humanoid = leader:FindFirstChild("Humanoid")
if not hum then return end
local path: Path = pfs:CreatePath({ --initializing path config
AgentRadius = squadronSizes[squad], --getting the size of the squadron
AgentCanJump = true,
AgentCanClimb = false,
Costs = {
Player = 0 -- make the pathfinding completely ignore players
}
})
path:ComputeAsync(leader.PrimaryPart.Position, destination)
if path.Status == Enum.PathStatus.NoPath then warn("no path found >:( trying to get to "..tostring(destination)) return end
if path.Status == Enum.PathStatus.Success then
for _, waypoint in ipairs(path:GetWaypoints()) do
hum:MoveTo(waypoint.Position)
hum.MoveToFinished:Wait()
end
end
end
while task.wait() do
for _, squad in ipairs(squadrons) do
local leader
local squadOffsetTable = {} --saving the offsetTable so we can remove values as we assign offsets to the members
for _, value in ipairs(NPCOffsets[squad]) do
table.insert(squadOffsetTable, value)
end
for i, member : Model in squad:GetChildren() do --loop through all members of all squadrons
if not member:IsA("Model") or not member:FindFirstChild("HumanoidRootPart") then continue end -- making sure its a rig
if member:HasTag("Leader") then --[FOR SQUAD LEADERS]
local leaderHum : Humanoid = member:FindFirstChild("Humanoid")
if not leaderHum then continue end
leader = member
local desiredWaypoint = GetRandomWaypoint("hallway")
MoveLeader(squad, member, desiredWaypoint)
else --[FOR REGULAR SQUAD MEMBERS]
local hum : Humanoid = member:FindFirstChild("Humanoid")
local closestOffset
local closestIndex
local shortestDistance
for index, offset in ipairs(squadOffsetTable) do -- make the NPC move to the closest offset to its position
local memberPos = member.PrimaryPart.Position
local distance = (memberPos - offset).Magnitude
if shortestDistance == nil or distance < shortestDistance then
shortestDistance = distance
closestOffset = offset
closestIndex = index
end
end
if closestOffset then
table.remove(squadOffsetTable, closestIndex) -- remove the offset after the closest one is found to prevent multiple NPCS going to the same offset
hum:MoveTo(leader.PrimaryPart.Position - closestOffset)
else
warn("no available offset for member: "..member.Name)
end
end
end
end
end
this script looks very scary, but really im just trying to figure out how to make the members move with the leader
any advice / tips would be appreciated! ![]()
