What is the best method for debouncing?

Background

I have these parts called “rails” and I am creating a script to detect if a player is touching the rail at all. There is a boolean value inserted into every player called "IsTouchingRail". It is “hooked up” to both a .Touched and .TouchEnded event. When a body part touches the rail, it adds the part to a table, and when the touch has ended, it removes the body part from the table. When the table is empty, it sets the IsTouchingRail value to false.

The problem

This system works fine when you are just standing still on the "rail’ (part) but not when you are walking on it doesn’t. Here’s why: when your walking on a part, you lift up your foot the .TouchEnded event is fired and removes the part from the table, then it sees that you put there is nothing in the table, so it sets the value to false. At the same time, your other foot touches the part, but the .Touched event does not fire fast enough to set it back to true. This little delay is a problem because when your touching the rail, it’s supposed disable the ability to turn on your jetpack but because of this delay, you can turn on your jetpack.

Code

for i, rail in pairs(rails:GetChildren()) do
	rail.Touched:Connect(function(part)
		local player = game.Players:GetPlayerFromCharacter(part.Parent) --Verifies if the body part belongs to a player
		if player ~= nil and table.find(bodyPartTouching[player.Name],part) == nil then --Checks if player was found and makes sure index doesn't already
			--exist
			table.insert(bodyPartTouching[player.Name],1,part) --Inserts part into table
			if player.IsTouchingRail.Value == false then
				print("Player is now touching")
				player.IsTouchingRail.Value = true --Sets value
			end
		end
	end)
	rail.TouchEnded:Connect(function(part)
		local player = game.Players:GetPlayerFromCharacter(part.Parent)
		if player ~= nil then
			table.remove(bodyPartTouching[player.Name], table.find(bodyPartTouching[player.Name],part)) --Removes it from table
			if bodyPartTouching[player.Name][1] == nil then --Checks if table is empty
				player.IsTouchingRail.Value = false --Sets Value
			end
		end
	end)
end

My solution and question

As a solution to this problem I have decided that every part detected by .Touched should be needs to be debounced (for about 1/2 second) so the .TouchEnded can’t detect it too quickly. Now the question: What would be the best method of individually debouncing parts?

Should I use tick() and store the time the part was touched in a table?

  • Yes
  • No

0 voters

Are there alternate solutions to this problem?

My module might help you out? I am not really following with your code at the moment.

I originally wrote it to simplify teleport queues, but it’s useful in other scenarios. If you mask your “rails” with a hit-box, you can use my module to query for when a player enters/leaves said hit-box.

Here’s an example application:

-- Services
local ServerStorage = game:GetService("ServerStorage")


-- Variables
local PlayerTracker = require(ServerStorage.PlayerTracker)
local Tracker = PlayerTracker.new(--[[Path.to.BasePart]])


-- Functions
local function OnPlayerEntered(Player)
    -- ...
end

local function OnPlayerLeft(Player)
    -- ...
end


-- Setup
Tracker.PlayerEntered:Connect(OnPlayerEntered)
Tracker.PlayerLeft:Connect(OnPlayerLeft)

Tracker:StartTracking()
1 Like

I don’t think that’s the issue. I did some testing and here’s what I found:

Test script

local rails = game.Workspace.Rails
local bodyPartTouching = {}
local touching = {}

game.Players.PlayerAdded:Connect(function(player)
	bodyPartTouching[player] = {}
	touching[player] = false	
end)

for i, rail in pairs(rails:GetChildren()) do
	rail.Touched:Connect(function(part)
		local player = game.Players:GetPlayerFromCharacter(part.Parent) --Verifies if the body part belongs to a player
		if not player then return end
		
		if not table.find(bodyPartTouching[player], part) then --Checks if player was found and makes sure index doesn't already exist
			table.insert(bodyPartTouching[player], part) --Inserts part into table
			
			touching[player] = #bodyPartTouching[player] > 0
			--print("Touch", part, #bodyPartTouching[player])
		end
	end)
	
	rail.TouchEnded:Connect(function(part)
		local player = game.Players:GetPlayerFromCharacter(part.Parent)
		if not player then return end
		
		if part.Name == "HumanoidRootPart" then
			print("Huh?!")
		end
		
		table.remove(bodyPartTouching[player], table.find(bodyPartTouching[player], part)) --Removes it from table

		touching[player] = #bodyPartTouching[player] > 0
		
		if #bodyPartTouching[player] == 0 then --Checks if table is empty
			print("Completely ended")
		end
	end)
end

The HumanoidRootPart pretty much never fires the TouchEnded event, but usually fires the Touched event. This means it gets added to the list of touching limbs but never gets removed. You can verify this for yourself using the test script.

Here’s a version that just ignores that part:

Summary

local rails = game.Workspace.Rails
local bodyPartTouching = {}
local touching = {}

game.Players.PlayerAdded:Connect(function(player)
	bodyPartTouching[player] = {}
	touching[player] = false	
end)

for i, rail in pairs(rails:GetChildren()) do
	rail.Touched:Connect(function(part)
		if part.Name == "HumanoidRootPart"  then return end
		
		local player = game.Players:GetPlayerFromCharacter(part.Parent) --Verifies if the body part belongs to a player
		if not player then return end
		
		if not table.find(bodyPartTouching[player], part) then --Checks if player was found and makes sure index doesn't already exist
			table.insert(bodyPartTouching[player], part) --Inserts part into table
			
			local wasTouching = touching[player]
			touching[player] = #bodyPartTouching[player] > 0
			
			if touching[player] and not wasTouching then
				print("Began touching")
			end
		end
	end)
	
	rail.TouchEnded:Connect(function(part)
		local player = game.Players:GetPlayerFromCharacter(part.Parent)
		if not player then return end
		
		table.remove(bodyPartTouching[player], table.find(bodyPartTouching[player], part)) --Removes it from table

		touching[player] = #bodyPartTouching[player] > 0
		
		if #bodyPartTouching[player] == 0 then --Checks if table is empty
			print("Touch completely ended")
		end
	end)
end

With that it alternates between touching and not touching, which is the expected behavior:

  ...
  Touch completely ended
  Began touching
  Touch completely ended
  Began touching
  Touch completely ended
  Began touching

It’s way too sensitive though, just walking on a surface causes it to switch several times per second. This is because the default walking animation causes the feet to actually start and stop touching the ground. T he way I’d fix it is with an additional “detector part” for detecting if the player is standing on a surface:

image

It’s welded to the character so that it juuuust sticks into the ground.

1 Like

So you’re saying that because of the HumanoidRootPart it adds it to the table but never gets removed?

Also, how do you weld a part to a player’s foot like that?

Thank you! However, I’m not sure if you know this, but this is about when the player touched a part, not when the player enters a hitbox (Sorry if this comes across as a little rude, I have no intentions). But also can this work for a problem regarding touching a part? If so, can you clarify and provide some explanation regarding the methods you used and how they work?

1 Like

Can this work for a problem regarding touching a part?

Yes. That is why I sent it to you. You can add a small overlay that the player’s feet can enter. Or, swap out GetPartBoundsInBox with GetPartsInPart.

By overlay, do you mean a part that is only slightly bigger than the original part. And can you please go into detail about how your methods that you use work?

Yes, that’s what I mean by overlay.

The general idea is to study the limbs of characters present within the defined space. If a specific set of limbs no longer appear in the next study, we know that the relative character has left said defined space.

1 Like

Also, could you please at least comment your code so I can better understand it?