How to reduce lag on this speedrun trigger script?

Hello developers,
I’m currently making a speedrun game and I need to have code run when the player enters certain parts, such as the starting line, checkpoints, kill parts, etc. Since .Touched is unreliable, I have tried to use the new GetPartsInPart API. Keep in mind, I ONLY need to detect the player in these instances, and not any other parts. I have written the following code:

while true do
	coroutine.wrap(function()
		for _, part in pairs(CollectionService:GetTagged("StartPoint")) do
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do

				local player

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then
					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if player:FindFirstChild("PlayerData") and player:FindFirstChild("PlayerData"):FindFirstChild("Level") then

							TimerStart(player)

							humanoid.WalkSpeed = part:GetAttribute("WalkSpeed") or 70
							humanoid.JumpPower = part:GetAttribute("JumpPower") or 50

							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							player.PlayerData.Level.Value = 1
						end
					end
				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("Checkpoint")) do
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then

					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if part:GetAttribute("Level") < player.PlayerData.Level.Value - 1 then
							player.Character.Head:Destroy()

							print("PLAYER "..player.Name.." HAS DIED AT LEVEL "..player.PlayerData.Level.Value)
						else
							humanoid.WalkSpeed = part:GetAttribute("WalkSpeed") or 70
							humanoid.JumpPower = part:GetAttribute("JumpPower") or 50

							player.PlayerData.Level.Value = part:GetAttribute("Level")

							local CoinDataStore = DataStore2("coins", player)
							local GemDataStore = DataStore2("gems", player)
							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								GemDataStore:Increment(1)

								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							CoinDataStore:Increment(2)

							print("PLAYER "..player.Name.." HAS REACHED LEVEL "..player.PlayerData.Level.Value)
						end
					end

				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("EndPoint")) do
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then

					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if part:GetAttribute("Level") < player.PlayerData.Level.Value - 1 then
							player.Character.Head:Destroy()
						else

							local finalTime

							if timers[player] then
								finalTime = TimerEnd(player, timers[player])
								if finalTime then
									CompletedEvent:Fire(player, finalTime)
								end
							end

							local CoinDataStore = DataStore2("coins", player)
							local GemDataStore = DataStore2("gems", player)
							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								GemDataStore:Increment(1)
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							if WorldsDataStore:Get()[WorldID].WorldCompleted == false then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].WorldCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							if WorldsDataStore:Get()[WorldID].BestTime < finalTime then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].BestTime = finalTime
								WorldsDataStore:Set(worlds)
							end

							CoinDataStore:Increment(10)

							--TODO -> GUI Code

							wait(4)

							game:GetService("TeleportService"):Teleport(7261940734, player)

						end
					end

				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("KillPart")) do
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then
					humanoid.Health = 0
				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("CoinSpawner")) do
            -- Gotta make these at some point
		end

	end)()
	
	wait()
end

However, it is EXTREMELY laggy. I’ve tried using RunService.Heartbeat, which is laggier, I’ve tried increasing the wait time, which causes lag spikes every few seconds, I’ve tried a ton of stuff to get the lag down. How could I go about refactoring this system to be less laggy and more reliable at the same time? Thanks!

Dumb question maybe, but why is Touched unreliable? It seems to work great for me, and I think it is used for most projectiles which are moving at extremely high velocities.

2 Likes

If you play Speedrun 4 for a little bit, you’ll notice that sometimes when you step on the start pad it doesn’t give you the speed boost and you inevitably die. The same is true in the game I’m currently making; around 20% of the time you step on the pad, Touched doesn’t fire and you also inevitably die. I can’t explain it, I don’t know how, all I know is that it happens. I’ve experienced this in other projects of mine and others’ as well.

EDIT: It also doesn’t work on parts with CanCollide off, which is what I’ve started using.

I haven’t played speed run, but can you use a plane or block that the player can go through rather than requiring a step plate? A plane would require CanTouch and could catch any body part. Expecting touch on a part requires gravity and the players body mover to cooperate and hit the part. It seems like that would occasionally miss, especially at higher velocities.

I feel like either way .Touched is still your best option here. It only fails due to the developers not using it correctly, avoid obsessive debounces and have multiple checks not just for the legs. You could try using Region3 though I don’t think that would be any better performance wise and may be even worse.

while true do task.wait()
	coroutine.wrap(function()
		for _, part in pairs(CollectionService:GetTagged("StartPoint")) do task.wait()
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do task.wait()

				local player

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then
					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if player:FindFirstChild("PlayerData") and player:FindFirstChild("PlayerData"):FindFirstChild("Level") then

							TimerStart(player)

							humanoid.WalkSpeed = part:GetAttribute("WalkSpeed") or 70
							humanoid.JumpPower = part:GetAttribute("JumpPower") or 50

							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							player.PlayerData.Level.Value = 1
						end
					end
				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("Checkpoint")) do task.wait()
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do task.wait()

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then

					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if part:GetAttribute("Level") < player.PlayerData.Level.Value - 1 then
							player.Character.Head:Destroy()

							print("PLAYER "..player.Name.." HAS DIED AT LEVEL "..player.PlayerData.Level.Value)
						else
							humanoid.WalkSpeed = part:GetAttribute("WalkSpeed") or 70
							humanoid.JumpPower = part:GetAttribute("JumpPower") or 50

							player.PlayerData.Level.Value = part:GetAttribute("Level")

							local CoinDataStore = DataStore2("coins", player)
							local GemDataStore = DataStore2("gems", player)
							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								GemDataStore:Increment(1)

								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							CoinDataStore:Increment(2)

							print("PLAYER "..player.Name.." HAS REACHED LEVEL "..player.PlayerData.Level.Value)
						end
					end

				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("EndPoint")) do task.wait()
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do task.wait()

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then

					local player = Players:GetPlayerFromCharacter(touchingPart.Parent)
					if player then
						if part:GetAttribute("Level") < player.PlayerData.Level.Value - 1 then
							player.Character.Head:Destroy()
						else

							local finalTime

							if timers[player] then
								finalTime = TimerEnd(player, timers[player])
								if finalTime then
									CompletedEvent:Fire(player, finalTime)
								end
							end

							local CoinDataStore = DataStore2("coins", player)
							local GemDataStore = DataStore2("gems", player)
							local WorldsDataStore = DataStore2("worlds", player)

							if WorldsDataStore:Get()[WorldID].LevelsCompleted < part:GetAttribute("Level") then
								GemDataStore:Increment(1)
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].LevelsCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							if WorldsDataStore:Get()[WorldID].WorldCompleted == false then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].WorldCompleted = part:GetAttribute("Level")
								WorldsDataStore:Set(worlds)
							end

							if WorldsDataStore:Get()[WorldID].BestTime < finalTime then
								local worlds = WorldsDataStore:Get()
								worlds[WorldID].BestTime = finalTime
								WorldsDataStore:Set(worlds)
							end

							CoinDataStore:Increment(10)

							--TODO -> GUI Code

							task.wait(4)

							game:GetService("TeleportService"):Teleport(7261940734, player)

						end
					end

				end

			end
		end

		for _, part in pairs(CollectionService:GetTagged("KillPart")) do task.wait()
			local touchingParts = workspace:GetPartsInPart(part)

			for i, touchingPart in pairs(touchingParts) do task.wait()

				local humanoid = touchingPart.Parent:FindFirstChild("Humanoid")
				if humanoid then
					humanoid.Health = 0
				end

			end
		end

		--[[
		for _, part in pairs(CollectionService:GetTagged("CoinSpawner")) do task.wait()
            	 	Gotta make these at some point
		end
		]]
	end)()
end

I didn’t really add much to improve your code, but I added yielding

it shouldn’t be as laggy as before

EDIT: I also made the last for loop a comment because it doesn’t do anything yet

1 Like

cc @YosemiteMe

I converted my parts to a .Touched event, and it works perfectly! I didn’t at all know about the CanTouch property before, so I thought it wouldn’t work. Thanks!