I've made this entity system for a game I'm making, and it hold around 300-600 enemies before lagging anyone know how to optimize it more (the enemies are on client server sends client position to update it)
server:
local mob = {} local ReplicatedStorage = game:GetService("ReplicatedStorage") local Events = ReplicatedStorage:WaitForChild("Events") local LastUpdate = tick() local UpdateDT = 0.1 local ClientInfo = {} shared["Positions"] = {} local MobsFolder =ReplicatedStorage:WaitForChild("Mobs") local mysteryMob = { ["Mystery"] = {MobsFolder:WaitForChild("Zombie"), MobsFolder:WaitForChild("Speedy"), MobsFolder:WaitForChild("Boss"), MobsFolder:WaitForChild("Spy")} } game:GetService("RunService").Heartbeat:Connect(function(DeltaTime) local map = workspace:WaitForChild("Map") local success, errorMsg = pcall(function() for number, data in pairs(shared["Positions"]) do if not data or not number or not data[3] then continue end local CurrentWaypoint = map.Waypoints:FindFirstChild(data[1]) local OldWaypoint = map.Waypoints:FindFirstChild(data[1]-1) or map.Start if not CurrentWaypoint or data[2].Y <= 0 then if mysteryMob[data.Name] and CurrentWaypoint then mob.Mystery(data, OldWaypoint.CFrame:Lerp(CurrentWaypoint.CFrame, data[3]), data[3], data[1]) end if not CurrentWaypoint then map.Base.Humanoid:TakeDamage(data[2].Y) end ClientInfo[number] = {Dead = true} Events.UpdatePosition:FireAllClients(ClientInfo) shared["Positions"][number] = nil shared["currentMobs"] -= 1 continue end local distance = (OldWaypoint.Position - CurrentWaypoint.Position).Magnitude data[3] += DeltaTime * data[2].X/50 / distance if data[3] >= 1 then data[1] += 1 data[3] = 0 end ClientInfo[number] = {Vector2int16.new(math.floor(CurrentWaypoint.Position.X*50), math.floor(CurrentWaypoint.Position.Z*50)), data[2].Y} end end) if not success then warn(errorMsg) end if success and tick() - LastUpdate >= UpdateDT then LastUpdate = tick() Events.UpdatePosition:FireAllClients(ClientInfo) end end) local num = 0 shared["currentMobs"] = 0 function mob.Spawn(name, quantity, map) local Exists = ReplicatedStorage.Mobs:FindFirstChild(name) if Exists then for i = 1, quantity do num += 1 if shared["currentMobs"] < 0 then shared["currentMobs"] = 0 end shared["currentMobs"] += 1 shared["Positions"][num] = {1, Vector2int16.new(Exists.Config.Speed.Value*50, math.round(Exists.Config.Health.Value)), 0,} Events.Render:FireAllClients(name, num, false) task.wait(0.25) end end end function mob.Mystery(mob, cframe, moved, waypoint) local mystery = ReplicatedStorage.Mobs.Mystery if mysteryMob[tostring(mob.Name)] then local Table = mysteryMob[tostring(mob.Name)] local newMob = Table[math.random(1, #Table)] local oldwaypoint = workspace.Map.Waypoints:FindFirstChild(waypoint-1) or workspace.Map.Start local Waypoint = workspace.Map.Waypoints:FindFirstChild(waypoint) shared["currentMobs"] += 1 shared["Positions"][num] = {waypoint, Vector2int16.new(newMob.Config.Speed.Value*50, math.round(newMob.Config.Health.Value)), moved,} print(shared["Positions"][num].Moved) Events.Render:FireAllClients(newMob.Name, num, false, cframe) end end return mob
Client:
local ReplicatedStorage = game:GetService("ReplicatedStorage") local Events = ReplicatedStorage:WaitForChild("Events") local LastUpdate = tick() local ud = 0.0085 shared["Enemies"] = {} shared["Units"] = {} local positions = {} local UnitPositions = {} local map = workspace:WaitForChild("Map") local base = map:WaitForChild("Base") local PS = game:GetService("PhysicsService") local function animation(object, anim) Events.AnimateZombie:Fire(object, anim) end Events.UpdatePosition.OnClientEvent:Connect(function(info, unitInfo) if info then positions = info elseif unitInfo then UnitPositions = unitInfo end end) local function moveEnemy(deltaTime) for Enemyobject, data in pairs(shared["Enemies"]) do if Enemyobject and positions[data] then local pos = positions[data] local Paosition = pos[1] if not Paosition or not Paosition.X or not Paosition.Y then shared["Enemies"] [Enemyobject] = nil Enemyobject:Destroy() continue end Enemyobject.Config.Health.Value = pos[2] local Position = Vector3.new(Paosition.X/50, Enemyobject.PrimaryPart.Position.Y, Paosition.Y/50) local Distance = (Enemyobject.PrimaryPart.Position - Position).Magnitude local speed = Enemyobject.Config.Speed.Value local cframe = CFrame.new(Paosition.X/50, Enemyobject.PrimaryPart.Position.Y, Paosition.Y/50) if Distance > 1 then Enemyobject.HumanoidRootPart.CFrame = CFrame.lookAt( Enemyobject.HumanoidRootPart.Position, Position) end Enemyobject:PivotTo(Enemyobject.PrimaryPart.CFrame:Lerp(cframe, deltaTime*speed/Distance)) end end end local function moveUnit(deltaTime) for UnitObject, data in pairs(shared["Units"] ) do if UnitObject and UnitPositions[data] then local pos = UnitPositions[data] local Paosition = pos[1] if not Paosition or not Paosition.X or not Paosition.Y then shared["Units"] [UnitObject] = nil UnitObject:Destroy() continue end UnitObject.Config.Health.Value = pos[2] local Position = Vector3.new(Paosition.X/50, UnitObject.PrimaryPart.Position.Y, Paosition.Y/50) local Distance = (UnitObject.PrimaryPart.Position - Position).Magnitude local speed = UnitObject.Config.Speed.Value local cframe = CFrame.new(Paosition.X/50, UnitObject.PrimaryPart.Position.Y, Paosition.Y/50) if Distance > 1 then UnitObject.HumanoidRootPart.CFrame = CFrame.lookAt(UnitObject.HumanoidRootPart.Position, Position) end UnitObject:PivotTo(UnitObject.PrimaryPart.CFrame:Lerp(cframe, deltaTime*speed/Distance)) end end end game:GetService("RunService").Heartbeat:Connect(function(deltaTime) if tick() - LastUpdate < ud then return end LastUpdate = tick() moveUnit(deltaTime) moveEnemy(deltaTime) end) Events.Render.OnClientEvent:Connect(function(name, EnemyNumber, unit, cframe) if not unit then local map = workspace:WaitForChild("Map") local start = map:WaitForChild("Start") local enemy = ReplicatedStorage.Mobs:FindFirstChild(name):Clone() enemy.Parent = workspace.Mobs if not enemy.PrimaryPart then enemy.PrimaryPart = enemy.HumanoidRootPart end enemy.PrimaryPart.Anchored = true enemy.PrimaryPart.CanCollide = false local zombieTag = Instance.new("BoolValue") zombieTag.Name = "ZombieTag" zombieTag.Parent = enemy for i, v in pairs(enemy:GetChildren()) do if v:IsA("BasePart") then PS:SetPartCollisionGroup(v, "Mob") end end enemy.PrimaryPart.CFrame = cframe or start.CFrame + Vector3.new(0, (enemy.PrimaryPart.Size.Y/2 - 1), 0) shared["Enemies"] [enemy] = EnemyNumber animation(enemy, "Walk") else local map = workspace:WaitForChild("Map") local start = map:WaitForChild("End") local unit = ReplicatedStorage.Units:FindFirstChild(name):Clone() unit.Parent = workspace.Units if not unit.PrimaryPart then unit.PrimaryPart = unit.HumanoidRootPart end unit.PrimaryPart.Anchored = true unit.PrimaryPart.CanCollide = false local unitTag = Instance.new("BoolValue") unitTag.Name = "UnitTag" unitTag.Parent = unit for i, v in pairs(unit:GetChildren()) do if v:IsA("BasePart") then PS:SetPartCollisionGroup(v, "Unit") end end unit.PrimaryPart.CFrame = cframe or start.CFrame + Vector3.new(0, (unit.PrimaryPart.Size.Y/2 - 1), 0) shared["Units"] [unit] = EnemyNumber end end)