Dictionary of debounces?

I need to use the touched event for certain checkpoints in my game but inside the touched event i’m firing a remote event. The issue is the touched event will run 100s of times depending on how the player walks over the checkpoint and that would cause remote event exhaustion so i need the touched event to only run once, but the event is on the server so i cant simply add a debounce otherwise, if another player touches a checkpoint within that debounce, it wont register. I was told to make a dictionary which contained debounces in it and this is what i have so far

for i, checkpoint in pairs(ends) do
	local db = {}
	checkpoint.Touched:Connect(function(touch)
		local humanoid = touch.Parent:FindFirstChild("Humanoid")
		if humanoid and humanoid.Health > 0 then
			local player = Players:GetPlayerFromCharacter(touch.Parent)
			db[player.UserId] = true
			if player and db[player.UserId] == true then
				local leaderstats = player:WaitForChild("leaderstats")
				local highest_level = leaderstats.Highest_Level

				local end_number = tonumber(checkpoint.Name)
				if end_number - highest_level.Value == 1 then
					highest_level.Value = end_number
				end
				endtimer:FireClient(player) 
				db[player.UserId] = false
			end
		end
	end)
end

although this obviously wont work so im not sure how to go about this.

Try something like this:

-- services
local players = game:GetService("Players")

-- variables
local debounces = {}
local object = script.Parent

-- functions
object.Touched:Connect(function(hit)
	local player = players:GetPlayerFromCharacter(hit.Parent)
	
	if (not player) then
		return
	end
	
	local userId = player.UserId
	
	if (debounces[userId]) then
		return
	end

    -- code goes here
	
	debounces[userId] = true
	
	task.wait(1)
	
	debounces[userId] = nil
end)

You get the player from the :GetPlayerFromCharacter() function using the variable hit.Parent we check if the player exists, if it does continue, and if not then return (since the player doesn’t exist) then we get the player’s userId property and if it’s in the debounce then that means the player has pressed the ‘button’ before, if not then add the player’s userId property to the debounce table and remove it after 1 second (or whatever time you want).

1 Like

So everything you already have is correct, except you don’t set db[player.UserId] to false after a small duration, which makes your debounce dictionary pointless. You can simply add a task.wait for a few seconds after using :FireClient() or you can do what I do in this snippet below:

for _, checkpointPart in checkpoints do
	local recentlyTouchedPlayers = {}
	checkpointPart.Touched:Connect(function(hit)
		local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
		if humanoid and humanoid.Health>0 then
			local player = game.Players:GetPlayerFromCharacter(hit.Parent)
			if player and recentlyTouchedPlayers[player.UserId]==nil then
				recentlyTouchedPlayers[player.UserId] = true
				task.delay(5, function()
					recentlyTouchedPlayers[player.UserId] = nil
				end)
				
				local leaderstats = player:WaitForChild("leaderstats")
				local highestLevel = leaderstats and leaderstats:FindFirstChild("Highest_Level")
				if highestLevel==nil then
					return
				end
				
				local highestReachedLevel = highestLevel.Value
				local checkpointLevel = tonumber(checkpointPart.Name)
				if checkpointLevel-highestReachedLevel==1 then
					highestLevel.Value = checkpointLevel
				end
				
				endtimer:FireClient(player) 
			end
		end
	end)
end

I never had the chance to test it, but it should give an idea of what to do. I’m using a task.delay just in case the checkpoint increment code fails. Because if it did fail, the user’s id would never be removed from the dictionary.

2 Likes

if theres a wait of any sort wouldn’t that affect other players gameplay? like if someone else were to touch the checkpoint at the same time

This method of debounce works for what you want because it only affects those who touch the checkpoint. There’d be no issue if two
or more players touched the same checkpoint.

1 Like
for i, checkpoint in pairs(ends) do
	local db = {}
	checkpoint.Touched:Connect(function(touch)
		local player = Players:GetPlayerFromCharacter(touch.Parent)
		local humanoid = touch.Parent:FindFirstChild("Humanoid")
		if humanoid and humanoid.Health > 0 then

			if player and db[player.UserId] == nil then
				local leaderstats = player:WaitForChild("leaderstats")
				local highest_level = leaderstats.Highest_Level

				local end_number = tonumber(checkpoint.Name)
				if end_number - highest_level.Value == 1 then
					highest_level.Value = end_number
				end
				endtimer:FireClient(player) 
				db[player.UserId] = true
				task.delay(5, function()
					db[player.UserId] = nil
				end)
			end
		end
	end)
end

i modified it a little by moving the task.delay to after the other code so that wont affect anything right? also other than that it all works so thanks

I’d say it’s better to leave it where it was before because it’d be guaranteed to occur only once every five seconds per player. If the debounce related stuff occurs after, there’s the potential that it will run more than once (although the chance is very low to my knowledge). The chance of that happening is even greater/guaranteed if the leaderstat code yields or errors.

If i put it before it doesn’t work im not sure why

As long as you moved these lines:

db[player.UserId] = true
task.delay(5, function()
	db[player.UserId] = nil
end)

to after this if statement:

if player and db[player.UserId] == nil then

then it should work as expected.