Optimize pathfinding for a big amount of agents

(Bad English Warning)

Hello i am creating pathfinding Ai fo zombies in my game where amount of them is increases.

My code is working i am afraid of situations when amount of zombies is to huge that agents just stops walking.
Is there a way to prevent it?
Also there is problem that agent does not want to fall from the edge.

I will be pleased with any comments!!!

local pathFindService = game:GetService("PathfindingService")
local players = game:GetService("Players")
local RunService = game:GetService("RunService")


function getNearestPlayer(Iam)
	local statuePart = workspace.Castle.Statue.PrimaryPart
	local referencePoint = workspace.BasePlate
	local nearestPlayer = nil
	local nearestDistance = math.huge
	for _, player in pairs(players:GetChildren()) do
		if player.Team == game:GetService("Teams").Spectators or player.Team == game:GetService("Teams")["In lobby"] then
			continue
		end
		local character = player.Character
		if character then
			local distance = (Iam.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude	
			if distance < nearestDistance  then
				nearestDistance = distance
				nearestPlayer = character
			end
		end
	end
	if nearestDistance > (Iam.HumanoidRootPart.Position - statuePart.Position).Magnitude then
		nearestDistance = (Iam.HumanoidRootPart.Position - statuePart.Position).Magnitude
		nearestPlayer = statuePart.Parent
	end
	return nearestPlayer,nearestDistance
end


local zombie = {}
	
zombie.__index = zombie

function zombie.Create(ZombieStats)

	local newZombie = {}
	newZombie.Character = ZombieStats.model:Clone()
	newZombie.Health = ZombieStats.health
	newZombie.Character.Humanoid.Health = ZombieStats.health
	newZombie.Character.Humanoid.MaxHealth = ZombieStats.health
	newZombie.WalkSpeed = ZombieStats.walkSpeed
	newZombie.Character.Humanoid.WalkSpeed = ZombieStats.walkSpeed
	newZombie.Range = ZombieStats.range
	newZombie.Damage = ZombieStats.damage
	newZombie.LastTargetPosition = nil
	newZombie.LastPathTime = 0
	
	
	newZombie.Target = nil
	
	
	task.spawn(function()
		newZombie.Character["Body Colors"].HeadColor3 = Color3.new(0,newZombie.Character["Body Colors"].HeadColor3.G-ZombieStats.colorChange,0)
		newZombie.Character["Body Colors"].LeftArmColor3 = Color3.new(0,newZombie.Character["Body Colors"].LeftArmColor3.G-ZombieStats.colorChange,0)
		newZombie.Character["Body Colors"].RightArmColor3 = Color3.new(0,newZombie.Character["Body Colors"].RightArmColor3.G-ZombieStats.colorChange,0)
	end)
	newZombie.Character.Parent = workspace.Zombies
	newZombie.Character.HumanoidRootPart.CFrame = ZombieStats.spawnPoint.CFrame
	
	task.spawn(function()
		local walkTrack = newZombie.Character.Humanoid.Animator:LoadAnimation(game:GetService("ReplicatedStorage").Animations.Zombie.Walk)
		walkTrack.Looped = true
		RunService.Heartbeat:Connect(function()

			if newZombie.Character.Humanoid.WalkToPoint ~= Vector3.new(0,0,0) then
				if not walkTrack.IsPlaying then
					walkTrack:Play()
				end
			end

		end)
	end)



	setmetatable(newZombie,zombie)
	return newZombie

end

function zombie:Attack()
	
	if self.Character:GetAttribute("debounce") then
		return
	end
	self.Character:SetAttribute("debounce",true)
	
	local attackTrack = self.Character.Humanoid.Animator:LoadAnimation(game:GetService("ReplicatedStorage").Animations.Zombie.Attack)
	attackTrack:Play()
	
	task.spawn(function()
		task.wait(1)
		self.Character:SetAttribute("debounce",false)
	end)
	
	local direction = self.Character.HumanoidRootPart.CFrame.LookVector
	local origin = self.Character.HumanoidRootPart.Position
	
	local rayCastParams = RaycastParams.new()
	rayCastParams.FilterType = Enum.RaycastFilterType.Exclude
	rayCastParams.FilterDescendantsInstances = {self.Character}
	
	local rayCastResult = workspace:Raycast(origin,direction*5,rayCastParams)

	if rayCastResult and rayCastResult.Instance then

		local hitPart = rayCastResult.Instance
		local humanoid = hitPart.Parent:FindFirstChild("Humanoid")
		if humanoid then

			if string.find(humanoid.Parent.Name,"Zombie") then
				humanoid:TakeDamage(1)
			else
				humanoid:TakeDamage(self.Damage)
			end
		
		else
			if hitPart.Parent == workspace.Castle.Statue then
				workspace.Castle.Statue.Health.Value -= self.Damage
			end
		end
	end
	
end



function zombie:Move()
	if self.Target then
		if (self.Character.HumanoidRootPart.Position-self.Target.HumanoidRootPart.Position).Magnitude > self.Range then
			pcall(function()
				local path = pathFindService:CreatePath()
				path:ComputeAsync(self.Character.HumanoidRootPart.Position,self.Target.HumanoidRootPart.Position)
				local Waypoints = path:GetWaypoints()
				print(path.Status)
				for _,waypoint in pairs(Waypoints) do
					if Waypoints and Waypoints[2] then
						local data = Waypoints[2]
						if data.Action == Enum.PathWaypointAction.Jump then
							self.Character.Humanoid.Jump = true
						end
						self.Character.Humanoid:MoveTo(data.Position)
					end
				end
			end)

		else
			self:Attack()
		end
	end
end



function zombie:FindTarget()
	
	while task.wait(0.225) do
		task.spawn(function()
			local nearestPlayer,distance = getNearestPlayer(self.Character)
			self.Target = nearestPlayer
			self:Move()
		end)
	end
end




return zombie
  • id look into spatial partioning for searching for targets
  • instead of creating a loop per-agent, have a loop outside of the creation of the agent that loops through all the created ones and updates them
  • try not to recompute the path every single update. Especially for agents that are farther away, they dont really need to be quickly adjusting their path
  • for attacking, it’s good to keep in mind that indexing properties of instances is pretty hefty when in mass. If you plan on lots of zombies attacking at once, try using variables for things like the humanoid and rootpart. This will also make it easier to check if they are nil before using them.
  • also you have a memory leak where it connects to heartbeat for updating the animations because the connection is never disconnected. Anyways this could be done in the update function to keep things orderly
1 Like

Thanks! I already closed memory leak
What Do you mean spatial partioning for searching for targets?

I believe it’s just splitting up a big area into smaller areas, although I wouldn’t necessarily know how that would optimize pathfinding for a lot of agents.

Perhaps it reduces the space searched to find a waypoint for the pathfinding?

Here are some useful resources I found about it:
Wikipedia
Stack Exchange

1 Like