Alright so I’m the process of making a wave game and what I see is that there’s so many monsters in the studio and the fact there’s so many monsters it causes the game to lag so what I found is that from my research. There are simple tricks you can do the make the game less laggy and optimize
So instead of putting folder some people in the forum recommended using models did that and I still have some lags problem what I did is basically when my monster would clone in the workspace, it would contain each 2 scripts 1 for the Health, and one for pathfinding and rushing to character. So I tried making a whole modulescript that would be responsable for all the slimes that would act as if each slime has the 2 scripts but centralized in one modulescript that could be simply called by 1 ServerScript
Here’s the code of ModuleScript of SlimeController :
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local MiniSplitSlime = ReplicatedStorage.Enemies.MiniSplitSlime
local SlimeController = {}
local EnemiesFolder = nil
local activeSlimes = {}
local spawnRadius = 50
local jumpInterval = 1.5
local jumpDistance = 3
local separationDistance = 10
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)
local healthUpdateEvent = ReplicatedStorage:WaitForChild("HealthUpdateEvent")
local crystalFolder = ReplicatedStorage:WaitForChild("Crystals")
local crystalModel = crystalFolder:FindFirstChild("Tier 1")
local crystalMeshPart = crystalModel:FindFirstChild("Common Crystal Tier 1")
function SlimeController:CreateHealthBar(enemy, humanoid, rootPart)
local healthBarGui = Instance.new("BillboardGui")
healthBarGui.Size = UDim2.new(0, 100, 0, 10)
healthBarGui.Adornee = rootPart
healthBarGui.StudsOffset = Vector3.new(0, 5, 0)
healthBarGui.Parent = enemy
local container = Instance.new("Frame")
container.Size = UDim2.new(1, 0, 1, 0)
container.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
container.BorderSizePixel = 0
container.Parent = healthBarGui
local redBar = Instance.new("Frame")
redBar.Size = UDim2.new(1, 0, 1, 0)
redBar.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
redBar.BorderSizePixel = 0
redBar.Parent = container
local healthBar = Instance.new("Frame")
healthBar.Size = UDim2.new(1, 0, 1, 0)
healthBar.BackgroundColor3 = Color3.fromRGB(0, 255, 0)
healthBar.BorderSizePixel = 0
healthBar.Parent = container
return healthBar
end
function SlimeController:HandleDamage(player, enemy, damageAmount, containPoison)
local slimeData = activeSlimes[enemy]
if not slimeData then return end
local humanoid = slimeData.Humanoid
local healthBar = slimeData.HealthBar
if humanoid.Health <= 0 then
local cloneMeshPart = crystalMeshPart:Clone()
cloneMeshPart.Position = slimeData.HRP.Position
cloneMeshPart.Anchored = true
cloneMeshPart.CanCollide = false
cloneMeshPart.Parent = workspace.Crystals
local originalPosition = cloneMeshPart.Position
local crystalTween = TweenService:Create(cloneMeshPart, TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
Position = originalPosition + Vector3.new(0, 2, 0)
})
crystalTween:Play()
cloneMeshPart.Touched:Connect(function(hit)
local character = hit.Parent
local touchingPlayer = Players:GetPlayerFromCharacter(character)
if touchingPlayer then
touchingPlayer:WaitForChild("leaderstats"):WaitForChild("ExpPlayer").Value += touchingPlayer:WaitForChild("ExpGainPlayer").Value
crystalTween:Cancel()
cloneMeshPart:Destroy()
end
end)
player:WaitForChild("EnemiesKilled").Value += 1
player:WaitForChild("TotalEnemiesKilled").Value += 1
enemy:Destroy()
activeSlimes[enemy] = nil
if string.find(enemy.Name, "SplitSlime") and not string.find(enemy.Name, "MiniSlime") then
for i = 1, 2 do
local cloneSlime = MiniSplitSlime:Clone()
cloneSlime.Name = enemy.Name .. "_MiniSlime" .. i
cloneSlime.Parent = workspace.Enemies
local rootPart = cloneSlime:FindFirstChild("HumanoidRootPart")
local playerCharacter = player.Character
local playerRoot = playerCharacter and playerCharacter:FindFirstChild("HumanoidRootPart")
if rootPart and playerRoot then
local offset = Vector3.new(
math.random(5, 10),
0,
math.random(5, 10)
)
cloneSlime:SetPrimaryPartCFrame(playerRoot.CFrame * CFrame.new(offset))
local size = rootPart.Size
rootPart.Size = Vector3.new(size.X, size.Y + 4, size.Z)
else
warn("rootPart or playerRoot missing")
end
end
end
return
else
local healthPercentage = math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1)
healthBar.Size = UDim2.new(healthPercentage, 0, 1, 0)
end
end
function SlimeController:RegisterSlimes(monsterName: string)
EnemiesFolder = workspace:WaitForChild("Enemies")
for _, descendant in ipairs(EnemiesFolder:GetDescendants()) do
if descendant:IsA("Model") and descendant.Name:lower():find(monsterName) then
local hrp = descendant:FindFirstChild("HumanoidRootPart")
local humanoid = descendant:FindFirstChildOfClass("Humanoid")
if hrp and humanoid and not activeSlimes[descendant] then
local healthBar = self:CreateHealthBar(descendant, humanoid, hrp)
activeSlimes[descendant] = {
Model = descendant,
HRP = hrp,
Humanoid = humanoid,
HealthBar = healthBar,
LastJump = 0,
UniqueOffset = math.random()
}
local bg = Instance.new("BodyGyro")
bg.MaxTorque = Vector3.new(1e5, 1e5, 1e5)
bg.D = 1000
bg.P = 10000
bg.CFrame = hrp.CFrame
bg.Parent = hrp
activeSlimes[descendant].Gyro = bg
end
end
end
end
function SlimeController:GetClosestPlayer()
local closest = nil
local minDistance = math.huge
for _, player in ipairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local distance = (player.Character.HumanoidRootPart.Position - Vector3.new()).Magnitude
if distance < minDistance then
minDistance = distance
closest = player
end
end
end
return closest
end
function SlimeController:UpdateAll(deltaTime)
EnemiesFolder = workspace:WaitForChild("Enemies")
local player = self:GetClosestPlayer()
if not player or not player.Character then return end
local playerPos = player.Character.HumanoidRootPart.Position
for _, slimeData in pairs(activeSlimes) do
local slime = slimeData.Model
local hrp = slimeData.HRP
local now = tick()
if now - slimeData.LastJump >= jumpInterval then
slimeData.LastJump = now
local angle = now * 0.2 + slimeData.UniqueOffset
local offset = Vector3.new(math.cos(angle), 0, math.sin(angle)) * 2
local targetPos = playerPos + offset
local avoidance = Vector3.zero
for _, other in ipairs(EnemiesFolder:GetDescendants()) do
if other ~= slime and other:FindFirstChild("HumanoidRootPart") then
local dist = (hrp.Position - other.HumanoidRootPart.Position).Magnitude
if dist < separationDistance then
local dir = (hrp.Position - other.HumanoidRootPart.Position).Unit
avoidance += dir * ((separationDistance - dist) / separationDistance)
end
end
end
local finalDir = (targetPos - hrp.Position).Unit + avoidance
finalDir = finalDir.Unit
local jumpVector = finalDir * jumpDistance
local destination = hrp.Position + jumpVector
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {slime}
rayParams.FilterType = Enum.RaycastFilterType.Blacklist
if not workspace:Raycast(hrp.Position, jumpVector, rayParams) then
local tween = TweenService:Create(hrp, tweenInfo, {CFrame = CFrame.new(destination)})
tween:Play()
if slimeData.Gyro then
slimeData.Gyro.CFrame = CFrame.new(hrp.Position, hrp.Position + finalDir)
end
end
end
end
end
RunService.Heartbeat:Connect(function(dt)
SlimeController:UpdateAll(dt)
end)
healthUpdateEvent.OnServerEvent:Connect(function(player, damageAmount, enemyTouched, containPoison)
local slimeData = activeSlimes[enemyTouched]
if not slimeData then return end
local humanoid = slimeData.Humanoid
local healthBar = slimeData.HealthBar
if humanoid and humanoid.Health > 0 then
humanoid.Health = math.max(humanoid.Health - damageAmount, 0)
SlimeController:HandleDamage(player, enemyTouched, damageAmount, containPoison)
if containPoison then
local poisonDuration = 5
local poisonDamage = 1
local poisonInterval = 1
coroutine.wrap(function()
for i = 1, poisonDuration do
if humanoid.Health <= 0 then break end
task.wait(poisonInterval)
humanoid.Health = math.max(humanoid.Health - poisonDamage, 0)
local healthPercentage = math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1)
healthBar.Size = UDim2.new(healthPercentage, 0, 1, 0)
SlimeController:HandleDamage(player, enemyTouched, damageAmount, containPoison)
end
end)()
end
end
end)
return SlimeController
Here’s the code of ServerScript SlimeMonsters :
local SlimeController = require(script.Parent.Controller.SlimeController)
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
player:WaitForChild("IsPlaying").Changed:Connect(function(value)
if value == true then
workspace:WaitForChild("Enemies").DescendantAdded:Connect(function(descendant)
if descendant:IsA("Model") and descendant.Name:lower():find("slime") then
task.wait(1)
SlimeController:RegisterSlimes("slime")
elseif descendant:IsA("Model") and descendant.Name:lower():find("splitslime") then
task.wait(1)
SlimeController:RegisterSlimes("splitslime")
elseif descendant:IsA("Model") and descendant.Name:lower():find("poisonslime") then
task.wait(1)
SlimeController:RegisterSlimes("poisonslime")
elseif descendant:IsA("Model") and descendant.Name:lower():find("minisplitslime") then
task.wait(1)
SlimeController:RegisterSlimes("minisplitslime")
elseif descendant:IsA("Model") and descendant.Name:lower():find("bigslime") then
task.wait(1)
SlimeController:RegisterSlimes("bigslime")
end
end)
end
end)
end)
Here’s the result of the code below :