Touched works multiple times

Hello delevopers! I’ve encoutering a problem where touched works for multiple time, i’ve tried to add a debounce system but i think it doesn’t work

local buttonPart = script.Parent
local debounce = false 

local BASE_PRICE = 50
local PRICE_MULTIPLIER = 1.15

local function formatNumber(n)
	local val = math.floor(tonumber(n) or 0)
	local suffixes = {"", "K", "M", "B", "T", "Q", "Qi", "Sx"}
	if val < 1000 then return tostring(val) end

	local index = math.floor(math.log10(val) / 3)
	if index >= #suffixes then index = #suffixes - 1 end

	local shortValue = val / (10 ^ (index * 3))
	return string.format("%.1f%s", shortValue, suffixes[index + 1]):gsub("%.0", "")
end

local info = buttonPart:WaitForChild("Info")
local container = info:WaitForChild("Container")
local textLabel = container:WaitForChild("T_Cost")

local function forceShorten()
	local rawText = textLabel.Text
	local numericValue = tonumber(rawText:match("%d+%.?%d*")) 

	if numericValue and numericValue >= 1000 then
		local shortened = formatNumber(numericValue)
		if textLabel.Text ~= shortened then
			textLabel.Text = shortened
		end
	end
end

textLabel:GetPropertyChangedSignal("Text"):Connect(forceShorten)

buttonPart.Touched:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)

	if player and not debounce then
		debounce = true 

		local lstats = player:FindFirstChild("leaderstats")
		local cash = lstats and lstats:FindFirstChild("Cash")
		local stairs = player:FindFirstChild("StairsCount")

		if cash and stairs then
			local price = math.floor(BASE_PRICE * (PRICE_MULTIPLIER ^ stairs.Value))

			if cash.Value >= price then
				cash.Value -= price
				stairs.Value += 1
			end
		end

		task.wait(1.0) 
		debounce = false 
	end
end)

forceShorten()

Thanks in advance!

2 Likes

Hi! I’m not sure exactly what you mean but just walking over the buttonPart seems to work fine in playtesting. Could you explain what the problem is with more detail?

Is there more than one script running? Didn’t test anything here, but that looks like it should work.
The debounce logic looks right anyways for one player at a time. How is this being touched? Are you walking on it, standing on it for a bit, then moving away?

this should work, i dont see anything wrong with it really.

tip

use task.delay() instead of just task.wait() for better practice.

Try this.

buttonPart.Touched:Connect(function(hit)
if hit.Name ~= "HumanoidRootPart" then return end
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)

	if player and not debounce then
		debounce = true 

		local lstats = player:FindFirstChild("leaderstats")
		local cash = lstats and lstats:FindFirstChild("Cash")
		local stairs = player:FindFirstChild("StairsCount")

		if cash and stairs then
			local price = math.floor(BASE_PRICE * (PRICE_MULTIPLIER ^ stairs.Value))

			if cash.Value >= price then
				cash.Value -= price
				stairs.Value += 1
			end
		end

		task.wait(1.0) 
		debounce = false 
	end
end)

A few things to check with .Touched:

  1. Both parts need CanTouch = true (check Properties)
  2. At least one part needs to NOT be anchored, or one needs to be moving
  3. The event fires on the part you connect it to — make sure you’re connecting to the right one
  4. If checking for a player: game.Players:GetPlayerFromCharacter(hit.Parent)

If you need detection without physical collision, consider workspace:GetPartsInPart() with OverlapParams instead.

Hope that helps!

I’ve tried your solution:

  1. CanTouch is enabled on both parts
  2. I turned off anchor on main part but still the same issue
  3. The event is correct
  4. I use it in my script

here is script now

local buttonPart = script.Parent
local BASE_PRICE = 50
local PRICE_MULTIPLIER = 1.15
local COOLDOWN = 1.0

local activeCycles = {}

local function formatNumber(n)
	local val = math.floor(tonumber(n) or 0)
	local suffixes = {"", "K", "M", "B", "T", "Q", "Qi", "Sx"}
	if val < 1000 then return tostring(val) end
	local index = math.floor(math.log10(val) / 3)
	if index >= #suffixes then index = #suffixes - 1 end
	local shortValue = val / (10 ^ (index * 3))
	return string.format("%.1f%s", shortValue, suffixes[index + 1]):gsub("%.0", "")
end

local info = buttonPart:WaitForChild("Info")
local container = info:WaitForChild("Container")
local textLabel = container:WaitForChild("T_Cost")

local function forceShorten()
	local rawText = textLabel.Text
	local numericValue = tonumber(rawText:match("%d+%.?%d*")) 
	if numericValue and numericValue >= 1000 then
		local shortened = formatNumber(numericValue)
		if textLabel.Text ~= shortened then
			textLabel.Text = shortened
		end
	end
end

textLabel:GetPropertyChangedSignal("Text"):Connect(forceShorten)

buttonPart.Touched:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if not player or activeCycles[player.UserId] then return end

	activeCycles[player.UserId] = true

	task.spawn(function()
		while true do
			local isStillOn = false
			local parts = workspace:GetPartsInPart(buttonPart)
			for _, p in pairs(parts) do
				if p:IsDescendantOf(player.Character) then
					isStillOn = true
					break
				end
			end

			if not isStillOn then break end

			local lstats = player:FindFirstChild("leaderstats")
			local cash = lstats and lstats:FindFirstChild("Cash")
			local stairs = player:FindFirstChild("StairsCount")

			if cash and stairs then
				local price = math.floor(BASE_PRICE * (PRICE_MULTIPLIER ^ stairs.Value))
				if cash.Value >= price then
					cash.Value -= price
					stairs.Value += 1
				end
			end

			task.wait(COOLDOWN)
		end

		task.delay(0.1, function()
			activeCycles[player.UserId] = nil
		end)
	end)
end)

forceShorten()
1 Like

I tried it too, but sometimes button doesn’t even work, or it works but it’s even worse

Keep in mind the touched part, as a trigger, will fire when touched as you walk onto it at first.
It will not fire if you’re standing on it and not moving. It will again fire when you start to move.
In your case, there is a one second debounce after any touch is fired, it will not fire again at all.
That can be a bit misleading.

I analyzed your script carefully, and I’ve came to the conclusion nothing seems out of the ordinary, only plausible explanation I can think of is that you have multiple scripts of the same kind running. Please let us know if you found a solution and tell us what went wrong!

I’ve setup the same button system myself, with the .Touched event and the debounce, and I’ve play tested it extensively. I even added debug prints, tried different debounce implementations, and even used your character model. In isolation everything works fine, only one purchase triggers per step, exactly as expected.

Given that you’re still seeing multiple triggers, the issue may not be the script itself but one of these structural things in the game:

  1. Duplicate scripts - make sure there aren’t multiple copies of the purchase script anywhere.
  2. Multiple overlapping parts - check the button for invisible or cloned parts that might also fire .Touched

Based on my testing, the script logic itself works perfectly, any duplicate triggers in this case may be caused by environmental factors. For a more reliable system, I recommend filtering for the HumanoidRootPart and using a per-player debounce.

You were right, the problem was in my core server script, basically it’s listening to buyEvent.OnServerEvent and gives +1 step
and my button script just changes cash.Value and stairs.Value at touch.
It’s my first serious project, so I guess mistakes like these are bound to happen. Thanks everyone for help!

1 Like

For some reason they swapped of the signal behavior to deferred by default. That messes with lot of things it seems. Setting it back to default in the Workspace fixes a lot of theses sort of issues.