Help with Button and Touched Events

Hi! I’m working on a sci-fi puzzle-type game, and I’m having trouble with a button on the floor that activates when stepped on/item is placed on. It would continuously flicker on and off, acting as if the item has left the button when it has stayed put. To correct this, I’ve added a lot of (probably unnecessary) conditions and code to the script. I’m not very good at scripting, and there are still some issues. I was wondering if somebody could clean up this code and prevent the weird flickering thing.

Here’s the code inside the button:

Inside the model containing the button
local buttonpart = script.Parent.Button
local buttonpressed = script.Parent.ButtonPressed
local hitbox = script.Parent.Hitbox
local state = false

local db = false

local currentobject = nil

hitbox.Touched:Connect(function(hit)
	if currentobject == nil then
		if hit.Name == "HumanoidRootPart" or hit.Name == "Cube" or hit.Name == "HeavyObject" then
			local heavy = hit.Parent:FindFirstChild("ButtonHeavy") or hit.Parent.Parent:FindFirstChild("ButtonHeavy")
			local onbutton = hit.Parent:FindFirstChild("OnButton") or hit.Parent.Parent:FindFirstChild("OnButton")
			if heavy and onbutton then
				if heavy.Value == true then
					if state == false and onbutton.Value == false then
						if db == false then
							db = true
							currentobject = hit
							state = true
							wait(0.25)
							db = false
							script.Parent.Activated.Value = true
							hit.Parent.OnButton.Value = true
							buttonpart.CanCollide = false
							buttonpart.Transparency = 1
							buttonpressed.Transparency = 0
							buttonpressed.PointLight.Enabled = true
							buttonpart.StepOn:Play()
							for i,player in pairs(game.Players:GetPlayers()) do
								game.ReplicatedStorage.VibrateGamepad:FireClient(player, Enum.VibrationMotor.Small, 0.5, 0.2)
							end
							if hit.Name == "Cube" then
								hit.DecalPart1.Decal1.Color3 = BrickColor.new("Bright green").Color
								hit.DecalPart1.Decal2.Color3 = BrickColor.new("Bright green").Color
								hit.DecalPart2.Decal1.Color3 = BrickColor.new("Bright green").Color
								hit.DecalPart2.Decal2.Color3 = BrickColor.new("Bright green").Color
								hit.DecalPart3.Decal1.Color3 = BrickColor.new("Bright green").Color
								hit.DecalPart3.Decal2.Color3 = BrickColor.new("Bright green").Color
							end
						end
					end
				end
			end
		end
	end
end)

hitbox.TouchEnded:Connect(function(hit)
	if currentobject == hit then
		if state == true then
			if db == false then
				if hit.Name == "HumanoidRootPart" or hit.Name == "Cube" or hit.Name == "HeavyObject" then
					if hit.Name == "Cube" or hit.Name == "HeavyObject" then
						if hit.Parent.BeingCarried.Value == true then
							db = true
							state = false
							wait(0.25)
							db = false
							script.Parent.Activated.Value = false
							hit.Parent.OnButton.Value = false
							buttonpart.CanCollide = true
							buttonpart.Transparency = 0
							buttonpressed.Transparency = 1
							buttonpressed.PointLight.Enabled = false
							buttonpart.StepOff:Play()
							currentobject = nil
							hit.DecalPart1.Decal1.Color3 = BrickColor.new("Smoky grey").Color
							hit.DecalPart1.Decal2.Color3 = BrickColor.new("Smoky grey").Color
							hit.DecalPart2.Decal1.Color3 = BrickColor.new("Smoky grey").Color
							hit.DecalPart2.Decal2.Color3 = BrickColor.new("Smoky grey").Color
							hit.DecalPart3.Decal1.Color3 = BrickColor.new("Smoky grey").Color
							hit.DecalPart3.Decal2.Color3 = BrickColor.new("Smoky grey").Color
						end
					else
						db = true
						state = false
						wait(0.25)
						db = false
						script.Parent.Activated.Value = false
						hit.Parent.OnButton.Value = false
						buttonpart.CanCollide = true
						buttonpart.Transparency = 0
						buttonpressed.Transparency = 1
						buttonpressed.PointLight.Enabled = false
						buttonpart.StepOff:Play()
						currentobject = nil
					end
				end
			end
		end
	end
end)

If you have any solutions, I would greatly appreciate them. Thanks!

Due to the sheer complexity of your code, I have not bothered reading all of it; however, I believe the issue is lies in your misuse of the TouchEnded event. I generally do not advocate for the use of TouchEnded event because, like the Touched event, it accounts for every time a specific Touched event ends. As you probably already know, the Touched event fires every single time a new Touch event is triggered, implying that the TouchedEnded event should hypothetically fire the same number of times. Because of this, implementing the strategy of a debounce will not work as it does under normal circumstances.

Fortunately, there are many solutions to your problem. One immediate idea that comes to mind is to take advantage of spatial queries, which from my understanding has replaced the now deprecated Region3 functionalities (someone please correct me if I’m wrong about this). Here are a few articles that I believe may help you.
https://developer.roblox.com/en-us/api-reference/class/WorldRoot
https://developer.roblox.com/en-us/api-reference/function/WorldRoot/GetPartBoundsInBox

Up until this point, I admit I have never really experimented with this new feature before, but because of this post, I took up the opportunity to explore out of my own curiosity.

Using the concept of spatial queries, I have simulated exactly what you are trying to achieve. I have posted a video of the script in action, followed by the code itself and my own breakdown of it. I would have explained things in the video but I am currently not in the position to speak.

local button = script.Parent

local on = false

function changedFunction() --Function to play whenever the button is stepped on or off
	if on then
		print("on!")
		button.BrickColor = BrickColor.new("Bright green")
	else
		print("off")
		button.BrickColor = BrickColor.new("Really red")
	end
end

spawn(function() --Allows for the loop to run without interrupting any code past it
	while task.wait() do --Runs infintely checking to see if a character part exists in the given region
		local parts = workspace:GetPartBoundsInBox(CFrame.new(5.5, 2.75, 33.5), Vector3.new(4, 3.5, 4)) --Finds the parts existing in the region to check
		local found = false --Local logic variable to see if a character part is found within this loop
		for _, part in pairs(parts) do --Looping through the parts found in the region
			if part.Parent:FindFirstChildOfClass("Humanoid") then --If the part is a character part
				if on then --If the button is already found, you don't wanna call the fucntion again
					found = true --Just indicate that a character part HAS been found
				else --If the on is FALSE this means that someone JUST stepped on the part while it previously hadn't been
					on = true --Change on to true
					changedFunction() --Call the changed function because now it has changed
					found = true --Indicate that found is true
				end
			end
		end
		if not found and on then --If a character part wasn't found AND on is true, that means someone JUST stepped off the button
			on = false --Change on to false
			changedFunction() --Call the changed function because now it has changed
		end
	end
end)

The general procedure is as follows. First, I create a while loop that runs constantly. The idea behind this is that for each iteration I will check to see if a character (or in your case, a desired object) is in a certain “region”. Next, within my while loop, I use WorldRoot:GetPartBoundsInBox. This method is used to find any existing parts within a certain region given a position and a size of the region. Refer to the second article I linked. Then, I loop through the table of parts (retrieved from the method) looking for a character part and if there is one, I know that the button should be activated if not already. Using some conditionals and logic, I narrow down when to call the function that indicates that the button has changed in its activation state. The final condition outside of the for loop just checks to see if someone just stopped pressing the button.

A lot of this logic can be very confusing, so if you have any questions regarding the purpose of variables or why I did something, do not hesitate to ask! Also, note that there are many ways to achieve your goal, and given that I just put this together on the spot, I may not have done this as efficiently as I should have, so keep an open mind to any other possible ideas!

1 Like

Thank you so much! This was very helpful and learning about spatial queries is probably going to help me even more later. Thanks!

1 Like

I gotta -1 this solution because of the use of spawn and a task.wait() loop, along with running expensive spatial query functions in a tight loop like this - it’s begging for performance problems and certainly does not scale well with multiple things doing this. At the very least it should be using RunService.Stepped, and checking far less often than ~30 Hz with wait().

1 Like

Yeah, efficiency was my main concern when creating this, but I could not come up with another reasonable solution that wouldn’t be even more expensive than this one. You are correct in that if one was to take this approach, he/she should at least try to make ir as cost effective as possible. Thank you for your input. I will keep this in mind.

1 Like