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)
