[SOLVED] Optimizing 100+ NPCs (strange issues)

In my game I have a horde survival system, meaning that lots of NPCs are attacking players, and they need to survive.

Everything is great with a lot of NPCs, I have low ping and high fps, as you can see here

The problem is, I have an NPC called “Slime Boss”, and as soon as this NPC starts getting spawned, I get incredible amount of lag, ping becomes 400+ and fps drops to 5-12, as you can see here

The weird thing for me, is that Slime Boss uses almost the same script as the other NPCs, so the issue is not his script. The issue is his size (he is 4-5 times bigger than others), which as far as I’m concerned, shouldn’t cause lag, because number of polygons matter, not the sheer size.

How to optimize this NPC without shrinking him?

3 Likes

The only thing I can think of that may be causing problems in your situation for that Boss Slime to cause problem is either collision problems (either from touched or collisionfidelity) or possibly just too many parts at once. The CPU usage from the different screenshots is doing something 12x more than usual, which means it’s computing a lot of something which I believe is something physical going on or possibly something else visually.

3 Likes

Here is a “Green Slime” NPC script, which doesnt lag at all

local char = script.Parent
local hum = char:WaitForChild("Humanoid")
local oldHealth = hum.Health
local damGUI = char:WaitForChild("Head"):WaitForChild("DamGUI")
local root = char:WaitForChild("HumanoidRootPart")
local attackRange = char:WaitForChild("AttackRange")
local damage = 10

local possibleTargets = game.ServerStorage:WaitForChild("ActivePlayers"):GetChildren()

if #possibleTargets > 1 then
	target = possibleTargets[math.random(1,#possibleTargets)].Value:WaitForChild("HumanoidRootPart")
elseif #possibleTargets == 1 then
	target = possibleTargets[1].Value:WaitForChild("HumanoidRootPart")
end


attackRange.Touched:Connect(function(hit)

	if hit.Parent:FindFirstChild("PlayerVal") then
		attackRange.CanTouch = false
		hit.Parent:FindFirstChild("Humanoid").Health -= damage
		root.AssemblyLinearVelocity = root.CFrame.LookVector * -200
		task.wait(0.3)
		attackRange.CanTouch = true
	end

end)

hum.HealthChanged:Connect(function(newHealth)

	local damTaken = oldHealth - newHealth
	damGUI.StudsOffset = Vector3.new(math.random(0,1), math.random(0.5, 2), 0)
	damGUI.TextLabel.Text = tostring(damTaken)
	damGUI.Enabled = true
	oldHealth = newHealth
	task.wait(0.5)
	damGUI.Enabled = false

end)


hum.Died:Connect(function()
	
	task.wait(0.3)
	local newChar1 = game.ServerStorage.NPCs.Other.GreenSlime1:Clone()
	newChar1.Parent = char.Parent
	newChar1:MoveTo(char.PrimaryPart.Position + Vector3.new(0,5,0))
	local newChar2 = game.ServerStorage.NPCs.Other.GreenSlime1:Clone()
	newChar2.Parent = char.Parent
	newChar2:MoveTo(char.PrimaryPart.Position + Vector3.new(0,5,0))
	game.ReplicatedStorage.EnemyCount.Value += 2
	game.ReplicatedStorage.EnemyCount.Value -= 1
	char:Destroy()
end)


while task.wait(0.05) do
	hum:MoveTo(target.Position)
end

And here’s the BossSlime NPC script, which causes the enormous lag

local char = script.Parent
local hum = char:WaitForChild("Humanoid")
local oldHealth = hum.Health
local damGUI = char:WaitForChild("Head"):WaitForChild("DamGUI")
local root = char:WaitForChild("HumanoidRootPart")
local attackRange = char:WaitForChild("AttackRange")
local damage = 10

local possibleTargets = game.ServerStorage:WaitForChild("ActivePlayers"):GetChildren()

if #possibleTargets > 1 then
	target = possibleTargets[math.random(1,#possibleTargets)].Value:WaitForChild("HumanoidRootPart")
elseif #possibleTargets == 1 then
	target = possibleTargets[1].Value:WaitForChild("HumanoidRootPart")
end


attackRange.Touched:Connect(function(hit)

	if hit.Parent:FindFirstChild("PlayerVal") then
		attackRange.CanTouch = false
		hit.Parent:FindFirstChild("Humanoid").Health -= damage
		root.AssemblyLinearVelocity = root.CFrame.LookVector * -200
		task.wait(0.3)
		attackRange.CanTouch = true
	end

end)

hum.HealthChanged:Connect(function(newHealth)

	local damTaken = oldHealth - newHealth
	damGUI.StudsOffset = Vector3.new(math.random(0,1), math.random(0.5, 2), 0)
	damGUI.TextLabel.Text = tostring(damTaken)
	damGUI.Enabled = true
	oldHealth = newHealth
	task.wait(0.5)
	damGUI.Enabled = false

end)


hum.Died:Connect(function()
	
	task.wait(0.3)
	local newChar1 = game.ServerStorage.NPCs.Other.SlimeBoss1:Clone()
	newChar1.Parent = char.Parent
	newChar1:MoveTo(char.PrimaryPart.Position + Vector3.new(0,5,0))
	local newChar2 = game.ServerStorage.NPCs.Other.SlimeBoss1:Clone()
	newChar2.Parent = char.Parent
	newChar2:MoveTo(char.PrimaryPart.Position + Vector3.new(0,5,0))
	game.ReplicatedStorage.EnemyCount.Value += 2
	game.ReplicatedStorage.EnemyCount.Value -= 1
	char:Destroy()
end)


while task.wait(0.5) do
	hum:MoveTo(target.Position)
end




As you can see, they are almost the same script.

Other differences are.
Green Slime - 100hp, green color
Boss Slime - 500hp, red color

I just found out that the size is not whats causing the issue, because I shrinked boss slime, and lag still persisted.

I cannot find any other differences, even the rigs are completely the same, the only physical difference is the color

The problem I see is the code here:

attackRange.Touched:Connect(function(hit)

Using the Touched connection can still have the opportunity to cause lag in your game (these connections can fire 1/60th a second if you don’t have a good debouncer and your debouncer is faulty.) You’re using Touched on sphere-liked hitboxes and if you don’t change the collisionfidelty on these more complicated shapes it can cause extreme lag and frame drops which is a known issue people battled with scripting wise. I’m asssuming you’re using meshparts/unions/custom parts to make your slimes instead of the usual default roblox parts though.

If all else fails though you can pull up the Micro Profile to see what kind of unique problem is causing so much lag in your game.

image

4 Likes

Thanks, but what is the solution? Should I change the collisionfidelity to box, or better yet make the touching collider (attackrange) a box part?

One thing you should also try is testing it in roblox player, since studio runs a lot of stuff in the background, and is less optimized then player

1 Like

I think it might be the slime transparency. Once I was building a map and I thought about using transparent blocks for a fog effect. This caused extreme lag and I had to remove it. A long time later I forgot about this and tried to create the fog again, and I re experienced the lag result

For fog Im just using regular lighting Haze and Density, whish should not cause any performance issues. And if it does, it does not explain why there are issues with this NPC only

I meant that the issue might be the part transparency, which I said happened with my part based fog, and which seems to be present on your slime body parts, too. Multiple transparent parts cause lag on my device, I dont know why. I think it might be the intersection of transparent parts using more of the device.

I cant really tell if they are transparent from your images, but I pretended them to be. Aside from that I dont know what would be causing the issue.

I solved the lag, your answers have been very helpful.

What was causing the most lag, was the slime’s shape, ball, as I believe you mentioned somewhere. I myself mentioned that usually polygons determine lag, not size, but saying that I for some reason forgot how much more polygons do circles have compared to boxes)

So as first solution I changed it’s shape to box. It looks very ugly now, but thankfully game’s focus are not aesthetics.

Also, as you mentioned, .Touched event was causing lag too, not as much as shape, but especially when NPCs got closer to each other, there were significant spikes in ping and drops in FPS.
As solution to this, I disabled touched event on NPCs, and instead am checking touching on a hurtbox part of player. So basically instead of 100s of touched events, I am now using just one.

Overall now I am very satisfied with game’s performance. Thanks everyone for your input and help :slight_smile:

I did, I regret it, I understood nothing even after viewing microprofiler’s dev hub page 5 times :smiley:

I actually did have issues with this with my game as well. I use standard humanoid bodies, but I tried out using meshes so I could have materials on them, and they do have a bit more triangles than the default limbs.

When multiple of them gathered up in the same area and kept colliding with each other and me, it was causing my FPS to drop to ~40. To solve this, I used the microprofiler, which I really recommend for performance issues. I don’t understand many labels, but I do try and see what is taking up too long in the frame and then searching the label on the devforum. After a bit of googling I discovered it’s due to them being more complex of a mesh, even if smaller. Changing the collision’s fidelity from Default to Hull/Box greatly improved performance and fixed my issue.

To retain the mesh, you could use a MeshPart instead with the mesh of a sphere (heck, even one with less triangles if that doesn’t matter to you) and change the property of it. See if Hull works nicely, and if not, try Box as that’s the best you’re gonna get. In the chance you don’t want them colliding with the player, you could disable collisions and possibly add some knockback for easier target hitting. I personally decided to go with the overlap APIs as they were much more accurate than touch for my use-case with melee weapons.

There’s a really useful property under Studio Settings used just for visualizing this - “Show Decomposition Geometry”!
image

I know it has been solved already, but just wanted to give my thoughts on it :slight_smile:

7 Likes

Thank You! I had no idea what collision fidelity was until I made this post. I will definitely be coming back to your answer in the future, to further optimize my games ^ _ ^

2 Likes