Debounce Patterns

A debounce system is a coding technique that keeps a function from running too many times, or inputs from triggering multiple times. The following scripting scenarios illustrate debounce as a best practice.

Detecting Collisions

Assume you want to build a hazardous trap part that causes 10 damage when touched. A basic Touched connection and damagePlayer function may look like this:

Script - Damage Player

1local part = script.Parent
2
3local function damagePlayer(otherPart)
4 print(part.Name .. " collided with " .. otherPart.Name)
5
6 local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
7 if humanoid then
8 humanoid.Health = humanoid.Health - 10 -- Reduce player health
9 end
10end
11
12part.Touched:Connect(damagePlayer)
13

While logical at first glance, testing will show that the Touched event fires multiple times in quick succession based on subtle physical collisions.

To avoid causing excessive damage on initial contact, you can add a debounce system which enforces a cooldown period on damage through an instance attribute.

Script - Damage Player Using Debounce

1local part = script.Parent
2
3local RESET_TIME = 1
4
5local function damagePlayer(otherPart)
6 print(part.Name .. " collided with " .. otherPart.Name)
7
8 local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
9 if humanoid then
10 if not part:GetAttribute("Touched") then
11 part:SetAttribute("Touched", true) -- Set attribute to true
12 humanoid.Health = humanoid.Health - 10 -- Reduce player health
13 task.wait(RESET_TIME) -- Wait for reset duration
14 part:SetAttribute("Touched", false) -- Reset attribute
15 end
16 end
17end
18
19part.Touched:Connect(damagePlayer)
20

Triggering Sounds

Debounce is also useful when working with sound effects, such as playing a sound when two parts collide (Touched), or playing a sound on the Activated event when a user interacts with an on-screen button. In both cases, calling Sound:Play() starts playback from the beginning of its track and — without a debounce system — the sound may play multiple times in quick succession.

To prevent sound overlap, you can debounce using the IsPlaying property of the Sound object:

Script - Play Collision Sound Using Debounce

1local projectile = script.Parent
2
3local function playSound()
4 -- Find child sound on the part
5 local sound = projectile:FindFirstChild("Impact")
6 -- Play the sound only if it's not already playing
7 if sound and not sound.IsPlaying then
8 sound:Play()
9 end
10end
11
12projectile.Touched:Connect(playSound)
13
Script - Play Button Click Using Debounce

1local button = script.Parent
2
3local function onButtonActivated()
4 -- Find child sound on the button
5 local sound = button:FindFirstChild("Click")
6 -- Play the sound only if it's not already playing
7 if sound and not sound.IsPlaying then
8 sound:Play()
9 end
10end
11
12button.Activated:Connect(onButtonActivated)
13

Pickup Effects

Experiences often include collectible pickups in the 3D world such as medi-kits, ammo packs, and more. If you design these pickups to remain in the world for players to grab again and again, a "cooldown" time should be added before the pickup refreshes and reactivates.

Similar to detecting collisions, you can manage the debounce state with an instance attribute, and visualize the cooldown period by changing the part's Transparency.

Script - Health Pickup Using Debounce

1local part = script.Parent
2part.Anchored = true
3part.CanCollide = false
4
5local COOLDOWN_TIME = 5
6
7local function healPlayer(otherPart)
8 local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
9 if humanoid then
10 if not part:GetAttribute("CoolingDown") then
11 part:SetAttribute("CoolingDown", true) -- Set attribute to true
12 humanoid.Health = humanoid.Health + 25 -- Increase player health
13 part.Transparency = 0.75 -- Make part semi-transparent to indicate cooldown state
14 task.wait(COOLDOWN_TIME) -- Wait for cooldown duration
15 part.Transparency = 0 -- Reset part to fully opaque
16 part:SetAttribute("CoolingDown", false) -- Reset attribute
17 end
18 end
19end
20
21part.Touched:Connect(healPlayer)
22