This code is decreasing server performance

I have this snippet of code in every enemy in my game. For every enemy I add, I can feel and see the server performing slower and slower. Currently, I have 50 enemies in the game and I plan to double this amount. How can I readjust this code to make its performance better/faster/ less straining on the server?

for _, Object in pairs(Mob:GetChildren()) do
	if not Object:IsA("BasePart") then continue end
	Object.Touched:Connect(function(hit)
		if (Enemy.Health < 1) then return end
		if not debounce then
			debounce = true
			Damage(hit, debounce)
			task.wait(0.5)
			debounce = false
		end
	end)
end

This code is in the damage handler for enemy NPCs. It’s meant to detect if a player comes into contact with parts of the enemy, and if they do, deal damage to them if the cooldown is free.

1 Like

Is this the entire code? Could you show the entire code? It’s hard to debug a problem when you only can work with so little.

Also, is this script in every single NPC?

local _M = require(script.Parent.MobConfig)
local Mob = script.Parent
local Enemy = Mob.Humanoid
local Abb = require(game.ReplicatedStorage.ShortenNumber)
local attackTrack = Enemy:LoadAnimation(Mob.Animations.Arms)

local debounce = false

local function CreateObject(hum,dmg)
	local TweenService = game:GetService("TweenService")
	local ObjModel = Instance.new("Model", workspace.DebrisHolder)
	local Obj = Instance.new("Part", ObjModel)
	Obj.Material = Enum.Material.ForceField
	Obj.Transparency = 1
	Obj.Reflectance = 0.2
	Obj.Position = hum.Parent:FindFirstChild("Head").Position + Vector3.new(math.random(-1.6,1.6),2,math.random(-1.6,1.6))
	Obj.Anchored = true
	Obj.CanCollide = false
	Obj.Locked = true
	Obj.Shape = Enum.PartType.Ball
	Obj.Size = Vector3.new(1.5,1.5,1.5)
	local Gui = script.BillboardGui:Clone()
	Gui.Enabled = true
	Gui.Adornee = ObjModel:FindFirstChild("Part")
	Gui.TextLabel.Text = "-"..Abb.Abb2(dmg)

	Obj.Color = Color3.fromRGB(85, 85, 255)
	Gui.TextLabel.TextColor3 = Color3.new(0.333333, 0.333333, 1)
	Gui.TextLabel.TextSize = 40

	Gui.Parent = ObjModel

	local ObjPos = Instance.new("BodyPosition", Obj)
	ObjPos.Position = Vector3.new(0,7.85,0)
	local OT1 = TweenService:Create(Obj, TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), {Transparency = 0
	})OT1:Play()
	local GT1 = TweenService:Create(Gui.TextLabel, TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), {TextTransparency = 0
	})GT1:Play()
	GT1.Completed:Wait()
	local OT2 = TweenService:Create(Obj, TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), {Transparency = 1
	})OT2:Play()
	local GT2 = TweenService:Create(Gui.TextLabel, TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), {TextTransparency = 1
	})GT2:Play()
	GT2.Completed:Wait()
	return ObjModel
end

function Damage(hit, DamageCooldown)
	if game.Players:GetPlayerFromCharacter(hit.Parent) then
		local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
		if (not plr.Character) then return end
		local chr = plr.Character
		if (not chr:FindFirstChild("Humanoid")) then return end
		local hum = chr.Humanoid
		if chr:FindFirstChild("ForceField") then return end
		if hum.Health > 0 then
			attackTrack:Play()
			Enemy.Parent.Head.Hit.Playing = true
			chr:WaitForChild("CombatTag"):FindFirstChild("inCombat").Value = true
			chr:WaitForChild("CombatTag"):FindFirstChild("ResetTimer").Value = 20
			chr:WaitForChild("CombatTag"):FindFirstChild("Noob").Value = Mob.Parent.Name
			local dmg = math.random(_M.MobDamage, math.floor(_M.MobDamage*1.5))
			hum.Health -= dmg
			local ObjModel = CreateObject(hum,dmg)
			ObjModel:Destroy()
		end
	end
end

for _, Object in pairs(Mob:GetChildren()) do
	if not Object:IsA("BasePart") then continue end
	Object.Touched:Connect(function(hit)
		if (Enemy.Health < 1) then return end
		if not debounce then
			debounce = true
			Damage(hit, debounce)
			task.wait(0.5)
			debounce = false
		end
	end)
end

I made a few optimizations, so here’s some tips

  1. Always set parent last, here’s why
  2. Don’t use :FindFirstChild when you don’t need to, it’s not performant
  3. Don’t detect touches on every single part, create a single hitbox instead
  4. Reuse objects whenever possible (like TweenInfo, services, etc)
  5. Load animations using .Animator.

Code:

local TweenService = game:GetService("TweenService")

local _M = require(script.Parent.MobConfig)
local Abb = require(game.ReplicatedStorage.ShortenNumber)

local Mob = script.Parent
local Enemy = Mob.Humanoid
local attackTrack = Enemy.Animator:LoadAnimation(Mob.Animations.Arms)

local debounce = false
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)

local function CreateObject(hum, dmg)
	local ObjModel = Instance.new("Model")
	ObjModel.Parent = workspace.DebrisHolder
	
	local Obj = Instance.new("Part")
	Obj.Material = Enum.Material.ForceField
	Obj.Transparency = 1
	Obj.Reflectance = 0.2
	Obj.Position = hum.Parent.Head.Position + Vector3.new(math.random(-1.6,1.6),2,math.random(-1.6,1.6))
	Obj.Anchored = true
	Obj.CanCollide = false
	Obj.Locked = true
	Obj.Shape = Enum.PartType.Ball
	Obj.Size = Vector3.new(1.5,1.5,1.5)
	Obj.Parent = ObjModel
	
	local Gui = script.BillboardGui:Clone()
	Gui.Enabled = true
	Gui.Adornee = ObjModel.Part
	Gui.TextLabel.Text = "-"..Abb.Abb2(dmg)
	Obj.Color = Color3.fromRGB(85, 85, 255)
	Gui.TextLabel.TextColor3 = Color3.new(0.333333, 0.333333, 1)
	Gui.TextLabel.TextSize = 40
	Gui.Parent = ObjModel

	local ObjPos = Instance.new("BodyPosition")
	ObjPos.Position = Vector3.new(0,7.85,0)
	ObjPos.Parent = Obj
	
	local OT1 = TweenService:Create(Obj, tweenInfo, {Transparency = 0})
	OT1:Play()
	
	local GT1 = TweenService:Create(Gui.TextLabel, tweenInfo, {TextTransparency = 0})
	GT1:Play()
	GT1.Completed:Wait()
	
	local OT2 = TweenService:Create(Obj, tweenInfo, {Transparency = 1})
	OT2:Play()
	
	local GT2 = TweenService:Create(Gui.TextLabel, tweenInfo, {TextTransparency = 1})
	GT2:Play()
	GT2.Completed:Wait()
	
	return ObjModel
end

local function Damage(hit)
	local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
	local character = player and player.Character
	
	if character and character:FindFirstChildWhichIsA("ForceField") then
		return
	end
	
	local humanoid = character:FindFirstChildWhichIsA("Humanoid")
	
	if humanoid and humanoid.Health > 0 then
		attackTrack:Play()
		Enemy.Parent.Head.Hit.Playing = true
		
		local combatTag = character:WaitForChild("CombatTag")
		combatTag.inCombat.Value = true
		combatTag.ResetTimer.Value = 20
		combatTag.Noob.Value = Mob.Parent.Name
		
		local dmg = math.random(_M.MobDamage, math.floor(_M.MobDamage*1.5))
		humanoid.Health -= dmg
		
		local ObjModel = CreateObject(humanoid, dmg)
		ObjModel:Destroy()
	end
end

-- Create hitbox
local cframe, size = Mob:GetBoundingBox()

local hitbox = Instance.new("Part")
hitbox.Size = size
hitbox.CFrame = cframe
hitbox.Transparency = 1
hitbox.CanCollide = false
hitbox.Parent = Mob

local weldConstraint = Instance.new("WeldConstraint")
weldConstraint.Part0 = Mob.PrimaryPart
weldConstraint.Part1 = hitbox
weldConstraint.Parent = Mob

-- Detect touches
hitbox.Touched:Connect(function(hit)
	if (Enemy.Health < 1) then return end

	if not debounce then
		debounce = true

		Damage(hit)

		task.wait(0.5)
		debounce = false
	end
end)
1 Like