5.7.2 Debouncing

“Debouncing” is a programming technique that comes from the world of electrical hardware. As it turns out, manufacturing a mass produced “perfect” button that behaves exactly as we want it to is exceedingly difficult to do.

In reality, when you press a physical button on some electronic device, the button bounces open and closed several times over a short span of time. Usually a couple to tens of milliseconds.

We are not aware of this because we have no way to detect such minute oscillations and over such a short time span. But a processor IS fast enough to detect them. And it does. So every bounce gets registered as an event which in turn culminates in some inadvertent and unpredictable behavior.

In the case of our Robloxian, when a character’s part comes into contact with the world part, that event happens repeatedly and against multiple character parts as the character moves—each time firing off an event.

To fix that, we need to come up with a away to filter out those subsequent “bounces.”

The absolute simplest way is to just disconnect the event after a character has been detected.

Lua
local part = game.Workspace.Part
local connection --needs to be declared first to make it available in the function

connection = part.Touched:Connect(function(otherPart)
	local character = otherPart.Parent

	if character.ClassName == "Model" then
		print("Detected:", character, otherPart)
		
		connection:Disconnect()
		connection = nil
	end
end)

Obviously, this isn’t a suitable solution if you need it to detect characters more than once.

Another way is to set a timer or flag and ignore any subsequent events for a short time following that.

Lua
local part = game.Workspace.Part

local lastTouch = 0
local debounceTime = 0.5

part.Touched:Connect(function(otherPart)
	if tick() - lastTouch < debounceTime then return end
	
	local character = otherPart.Parent
	
	if character.ClassName == "Model" then
		lastTouch = tick()
		print("Detected:", character, otherPart)
	end
end)

On line 7, if the elapsed time since the last character was detected is less than the debounce time, the the function immediately returns, preventing any further checks.

If successful, that timer is reset at line 12.

One limitation with this method is that it locks out all characters from activating the block.

A variation we can make is to create list that tracks the last touch event per character and then just debounce it for that character.

Lua
local part = game.Workspace.Part

local lastTouch = 0
local debounceTime = 0.5
local characterLastDetected = {}

part.Touched:Connect(function(otherPart)
	local character = otherPart.Parent
	
	if character.ClassName == "Model" then
		if characterLastDetected[character.Name] and
			tick() - characterLastDetected[character.Name] < debounceTime 
		then
			return
		end
		
		characterLastDetected[character.Name] = tick()
		print("Detected:", character, otherPart)
	end
end)

At line 17, we set the time, then on line 12 perform the debounce check.

Since the character’s name is a string, we can use that as the key in our table. This generally works find for player characters but if you have multiple duplicate NPCs with the same name such as “Zombie” or just “Model” you will run into bugs.

It is possible to use the model directly as the key:

Lua
...
	characterLastDetected[character] = tick()
...

But this will likely lead to a memory leak. If your game consists of a lot of NPCs or respawning of player characters, I don’t recommend this method at our current experience level.

This has to do with garbage collection and weak tables, which mandate more than a footnote so we will revisit the subject at a later time.