How to reduce peformance issues in this loop?

I wrote a script a while back that is ultimately flawed in peformance… Due to multiple loops being inside of each other running every frame, while looping through the entire workspace. The game is unplayble.

I have attempted some ideas. For example only looping through the players by using a folder inside workspace to store players, however local parts = workspace:GetPartBoundsInBox(boxPos, boxSize) can only use workspace apparently. Which does confuse me a bit since this almost never seems like a viable function for any game peformance wise.

All of these ideas however, either don’t work as shown above, or even if I manage to get them to work, they won’t be enough to get the loop to run at a reasonable peformance.

My question is then how I should approach this? Do you see any peformance improvements that can be done to the current loop? I could write a new script that thinks about peformance, but without suggestions to solve issues like not being able to loop through a folder with GetPartBoundsInBox, it would probably be futile.

game:GetService("RunService").RenderStepped:Connect(function()
	for i, zone in pairs (soundZones) do
		local boxPos = zone.CFrame
		local boxSize = zone.Size
		local parts = workspace:GetPartBoundsInBox(boxPos, boxSize)
		for i, part in pairs (parts) do
			if part.Parent.Name == player.Name then
				found = true
				break
			else
				found = false
			end
		end
		
		if found then
			local sound = sounds:FindFirstChild(zone.Name)
			if sound and sound.IsPlaying == false then
				local check = sounds:GetChildren()
				for i,v in pairs(check) do
					v:Stop()
				end
				sound:Play()
				latestZone = zone.Name
				if game.Lighting.ClockTime == 15 then
					zoneText.Text = string.gsub(zone.Name, "Day", "")
				elseif game.Lighting.ClockTime == 3 then
					zoneText.Text = string.gsub(zone.Name, "Night", "")
				end
				zoneText.Position = UDim2.new(0,0,0.00,0)
				wait(0.1)
				zoneText.TextTransparency = 0
				zoneText:TweenPosition(UDim2.new(0,0,0.12,0))
				wait(2.5)
				while zoneText.TextTransparency < 1 do
					task.wait(0.05)
					zoneText.TextTransparency += 0.03
				end
			end
		else
			--code to give music when player joins in deadzone
		end
	end
end)
1 Like

You can loop from the Players service, a folder is not needed. Also is the statement “if found then”, running more than once?

2 Likes

yep… it does run about once every 2 frames or so.

how would looping through the players service be sat up here? It seems to me that local parts = workspace:GetPartBoundsInBox(boxPos, boxSize) is nessecary to detect anything at all

1 Like

What if you just create a big box, cover the entire zone in it, and make it transparent and turn off collisions?

Then you can check what zone the player is in by using Touched and TouchEnded.

1 Like

what are the capabilities of the function “Touched”
can it detect if a player teleports inside and other funky things needed in gameplay?

if so, it’s true that it would improve peformance a lot. Although the RenderStepped loop will still have to be turned into a function and ran when clocktime changes, since day/night has different music… but ~60x less laggy I suppose.

1 Like

Yep, as far as I know that’s how it should work. Any collision between them (whether that’s through teleportation, walking into it, etc.) should fire the Touched event. Whenever they leave that (same thing, teleportation, walking out, etc) should fire TouchEnded

1 Like

ok I will go and write the function and test it, thanks for the suggestion. Will write back with results.

2 Likes

hey again, I wrote a script that mostly works…
There is a bug which I think is caused by the humanoidrootpart not being local somehow? The “ran” (zoneDetected function) prints at random times (other than printing when a player moves to a zone), sometimes many times and sometimes 0 times, and it’s causing everything to be very buggy. The original method didn’t have this issue. I figured it would be local to each player, but apparently not? .Touched is a server event? What should I do to fix this issue.

function zoneDetected(zone)
	print("ran")
	--check the time of day
	if game.Lighting.ClockTime == 15 then
		zoneText.Text = string.gsub(zone.Name, "Day", "")
	elseif game.Lighting.ClockTime == 3 then
		zoneText.Text = string.gsub(zone.Name, "Night", "")
	end

	--check player's latestZone
	if latestZone ~= zone.Name then
		
		--stop and play music
		local sound = sounds:FindFirstChild(zone.Name)
		if sound and sound.IsPlaying == false then
			local check = sounds:GetChildren()
			for i,v in pairs(check) do
				v:Stop()
			end
			sound:Play()
		end
		
		--set latestZone
		print("statementRan")
		latestZone = zone.Name
		print(latestZone)
		
		--position and tween the zoneUI
		zoneText.Position = UDim2.new(0,0,0.00,0)
		zoneText.TextTransparency = 0
		zoneText:TweenPosition(UDim2.new(0,0,0.12,0))
		wait(2.5)
		while zoneText.TextTransparency < 1 do
			task.wait(0.05)
			zoneText.TextTransparency += 0.03
		end
	end
end

for index,zone in pairs(soundZones) do
	zone.Touched:Connect(function(hit)
		if hit.Name == "HumanoidRootPart" then
			zoneDetected(zone)
		end
	end)
end

edit: added if hit.Name == "HumanoidRootPart" and hit.Parent.Parent == game.Workspace.playerFolder then and I think it prevents other humanoids from interacting. Which hopefully removes the random function triggers (it’s not that easy to replicate so can’t say for sure it’s fixed), but the players still trigger each other’s music.

edit2: welllll I didn’t think this would actually work, but it does if hit.Name == "HumanoidRootPart" and hit.Parent.Parent == game.Workspace.playerFolder and hit.Parent.Name == player.Name then. Hopefully no more bugs, so I’m gonna tag your message as solved. If you have any more suggestions for this script, go ahead.

1 Like

You’re doing a lot of calculations every frame. I assume you’re trying to check if the player is in range of a sound? Instead of going from sounds towards checking the players, you should go from players towards checking the sounds.

1 Like

It’s checking if a player’s character is in a part (“zone”) with a name equal to a sound in soundService. .Touched works with minimal peformance fortunately. No large table looping.

I don’t know what you mean by “from players towards checking the sounds”, but the new .Touched version of the script does the same thing as the old, but without having to loop through a massive table (workspace), because you can physically detect it by touch event instead of that workspace:GetPartBoundsInBox function. And of course the new one minimizes the amount of loops needed (I tested it with 50+ parts in zones and it didn’t cause an issue in peformance window).

Here, I test the player against the zone, instead of the zone against the player.

1 Like
local function isPositionInsideZone(position, zoneMin, zoneMax)
	return position.x >= zoneMin.x and position.y >= zoneMin.y and position.z >= zoneMin.z and
		position.x <= zoneMax.x and position.y <= zoneMax.y and position.z <= zoneMax.z
end

does this function do the same as workspace:GetPartBoundsInBox(this obviously only checking player characters ofc), but without the lag? I just figured out blacklisting is a thing for workspace:GetPartBoundsInBox that makes sure not to loop through the whole workspace(?), which if true, would explain why I had trouble understanding the usage of it. Your version still seems better overall though.

Not sure if it works the same way, this is called axis-aligned bounding box testing (aabb)

1 Like

well, I will certainly try the method out when needed.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.