How to optimize tower system

ohhhh im sorry accident sometimes :sweat_smile:

1 Like

Wait do you mean Parallal Lua? Because I don’t have that much expensive operations, i do not think this will help that much.

The reason we have client side rendering is because it can run smoothly even if we have up to 1000 enemies. It’s just that the tower targeting is lagging the game. Without towers, there is next to no lag.

1 Like

Can anyone help? This is bugging me a lot

1 Like

Please ask me stuff if you are confused. This is my first reply so idk what to say here…
EDIT: Fixed some bugs and cut out some fluff to optimize it a bit more

NOTE: If “self.range” is calculated in studs then the range has to be dividided by two like this “self.range / 2” because 1 radius basically translates to 2 studs so be careful about that.

local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include

local function detectenemies(targetMode: "first"|"last")
	params.FilterDescendantsInstances = {map.enemies}
	local towerPos = model.PrimaryPart.Position
	local potentialEnemies = workspace:GetPartBoundsInRadius(towerPos, self.range / 2, params) --This gets all enemy parts that are in the range of the tower within a sphere (range is divided by two cuz i assume the range is in studs)
	local range: number? -- this range var is for getting the closest or the furthest enemy
	local target: Part?

	target = potentialEnemies[1]

	for i, potentialEnemyPart: Part in ipairs(potentialEnemies) do
		local distance = (towerPos - potentialEnemyPart.Position).Magnitude
		if not range then
			range = distance
		end

		if distance <= range and targetMode == "first" then
			range = distance
			target = potentialEnemyPart
		elseif distance >= range and targetMode == "last" then
			range = distance
			target = potentialEnemyPart
		end
	end

	return target and target:FindFirstAncestorOfClass("Model") --this can return nil if no enemy was found
	--^ NOTE: if the enemies have more models inside them this might return that model instead, you could fix this by ungrouping the models inside the enemy models
end
Old Version (don't use this, it has bugs and it sucks)
local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include

local function detectenemies(targetMode: "first"|"last")
	params.FilterDescendantsInstances = map:WaitForChild("enemies"):GetDescendants()

	local towerCFrame = model.PrimaryPart.CFrame
	local potentialEnemies = workspace:GetPartBoundsInRadius(towerCFrame.Position, self.range, params) --This gets all enemy parts that are in the range of the tower
	local range: number -- this range var is for getting the closest or the furthest enemy
	local target: Part

	target = potentialEnemies[1]

	for i, potentialEnemyPart: Part in pairs(potentialEnemies) do
		local distance = (target.Position - potentialEnemyPart.Position).Magnitude
		if not range then
			range = distance
		end

		if distance <= range and targetMode == "first" then
			range = distance
			target = potentialEnemyPart
		elseif distance >= range and targetMode == "last" then
			range = distance
			target = potentialEnemyPart
		end
	end

	if target then
		target.Destroying:Once(function()
			target = nil
		end)
	end
	
	return target:FindFirstAncestorOfClass("Model") --this can return nil if no enemy was found
	--^ NOTE: if the enemies have more models inside them this might return that model instead, you could fix this by ungrouping the models inside the enemy models
end

This might not improve that much to be honest, i tried my best. You can test it to see if it improves anything.

I would also like to inform you there is better ways to detect enemies than your solution so i would advise to think about how you handle towers so it can be fast and efficient, so please don’t hold back on trying more ways to handle your towers. I never tried something like this so i don’t have exprience in this stuff

1 Like

Our enemy system has client side rendering, so the position is refrenced of attrubute

1 Like

If you want to use the CFrame attribute then this is the modified version.
But the problem here is the hitbox of the tower is a square instead of a radius (sphere) which may not be what you want

local function detectenemies(targetMode: "first"|"last")
	local towerPos = model.PrimaryPart.Position
	local target, range
	for i, potentialEnemy: Model in pairs(enemies:GetChildren()) do
		if not target then
			target = potentialEnemy
		end
		local distance = (towerPos - potentialEnemy:GetAttribute("CFrame").Position).Magnitude
		if not range then
			range = distance
		end

		if distance <= range and targetMode == "first" then
			range = distance
			target = potentialEnemy
		elseif distance >= range and targetMode == "last" then
			range = distance
			target = potentialEnemy
		end
	end
	
	if (towerPos - target:GetAttribute("CFrame").Position).Magnitude > self.range then --checks if the target is out of range
		return nil
	end
	return target
end
1 Like

Also --!native probably doesn’t help in your case i don’t know much about it but it seems that your script doesn’t really need it and also because --!natively generated scripts take up more memory and resources so it is not recomended to use it in all of our scripts

here is when to use --!native
link to the post: Luau Native Code Generation Preview [Studio Beta]

You should target every 1-2 seconds, or if enemy is gone, then you can prevent mass spaming 100 per second

Sorry for late reply, but it locks onto an enemy and doesn’t go to any other enemy until the locked enemy dies.

Hmm… Can you tell me does the targeted enemy still stays targeted even when it exits the range? and also what does the “Exit” attribute for enemies mean?

It moves on after the targeted enemy leaves the range. Exit means the final destination for the enemy. Also I noticed that the range is a bit bigger than what the range is supposed to be.

I dont know how to fix why your range being a bit bigger but that may be because the code i sent detects in the shape of a square, so the code i sent previously could work if the positions are updated on the server.

But try this to fix the locked target problem, not sure if it will fix it but i am trying alright!

local function detectenemies(targetMode: "first"|"last")
	local towerPos = model.PrimaryPart.Position
	local target, range
	for i, potentialEnemy: Model in pairs(enemies:GetChildren()) do
		local distance = (towerPos - potentialEnemy:GetAttribute("CFrame").Position).Magnitude
		if distance > self.range then
			continue
		end

		if not target then
			target = distance
			range = distance
		end
		if (distance <= range) and (targetMode == "first") then
			range = distance
			target = potentialEnemy
		elseif (distance >= range) and (targetMode == "last") then
			range = distance
			target = potentialEnemy
		end
	end

	return target
end
1 Like

The biggest performance gains that you will get will probably be from changing the targeting algo from O(N^2). It’s not easy but a broadphase for target detection would be a big bonus. Maybe look into quad trees.

1 Like

I concur. A quadtree or octree is probably the best option for increasing performance significantly. I’m working on a tower defense myself right now and implementing an octree has increased performance by a lot.

And how would I implement an Octree? My enemies are rendered client sided

An octree is simply a spacial partitioning datastructure. It doesn’t matter whenever it’s clientside or serverside. It’s only important that it’s implemented on whatever side has the authority so that it can actually be used properly.

The use case here is that by grouping entities in separate nodes, it’s more efficient to search for a target because you’re no longer searching and comparing with every entity on the map, but only the entities inside the nodes that are in range.

As for implementation, I’d suggest you go search it up and try to learn for yourself - it will be much more useful if you can understand what is going on.

1 Like

Okay, thank you! Ima try to implement this

From my understanding, octtrees seem to be more for static objects? Because all the modules that are public all are for static uses.

You could try looking at this module.

Keep in mind that I haven’t tested it myself. Though it’s important to understand how octrees work and adapt them to your specific use-case.

Octrees, though best suited for static objects, are still really good for dynamic objects - in fact, Roblox uses one for their physics engine. Ideally, insertion and removal are O(logN), which is good enough since you’re not adding new entities every frame the entire game. The main performance boost is the fast searching (compared to a brute force method).

Octrees can seem daunting. That’s ok. Challenging yourself and persevering is the way to success!

1 Like

A quadtree might be better for your situation if the height level of the enemies and towers don’t matter to gameplay, like if your towers will hit enemies no matter the distance is height-wise. And it might have better performance than an octree aswell. But if you plan on including height/length as a part of the towers hit range then it’s a better idea to stick with the octree.

1 Like