How to immitate a ".Touched" event?

I’m doing a separate project for testing skills, which is basically a “Button Simulator” type game. I tried using the .Touched event for when a player touches a button, but it just doesn’t work well. The event doesn’t fire continuously; it only fires once, even when the player is standing on the part.

Also, I want it to be client-sided, and fire the server to update the stats and request the server for checking if the stat is greater than or equal to the minimum. I thought about using a Heartbeat connection for checking locally if the player is within at least 0.05 studs away from the part, but wouldn’t it be way too laggy? Imagine I have 300 buttons in the game; it would be massively laggy running through 300 buttons each frame.

That’s all I want to know. Thanks!

Imagine I have 300 buttons in the game; it would be massively laggy running through 300 buttons each frame.

This is the cost of doing your own touch detection system. If you really want to do this, I would suggest having one central heartbeat function (instead of 300 different ones) and do all your checks in there. Alternatively you could have a loop running every 0.05 seconds or however long you want doing the same checks. I would look into using GetPartBoundsInRadius(), as I believe it’s faster than calculating magnitude every frame.

1 Like

My plan was not creating 300 heartbeat functions, it was, indeed, one central one; however, it would still cost a lot of the memory.

Alright. I would still say that it’s going to be expensive no matter what you do, if you want to do it custom. I would also say only fire the events when there’s something notable.

1 Like

I may be trying to use RayCast, as I think this would be the best approach

Instead of checking every button every frame, you can check for buttons around the player, every frame. Since you’ll make it client sided, you only have to check around the character, that’s 1 operation instead of 300

You can use worldroot or raycasts (or both)

1 Like

Can you make an example? Like, a code to demonstrate what you mean? (I just figured out using Raycast would be kinda weird, since I would need to add various raycast casting to all directions)

1 Like

I’m currently hundreds of kilometers away from my pc, so that might be hard…

1 Like

How would I use raycast for this? A player could touch the button in all ways possible (feet, torso, head)

1 Like

What I think I did was use WorldRoot for um normal detection and a raycast (or blockcast) downwards to detect the ground better? Or something

An annoying thing is the when the player jumps continuously, he doesn’t always quite hit the ground before jumping again

Try with WorldRoot first probably

1 Like

As mentioned earlier, it’s inefficient to check every button in the game individually. A more optimized approach is to check only for buttons that are close enough the the local player.

Instead of relying on the .Touched event, which does fire “continously,” I reccomend using the :GetTouchingParts() method. This functionr eturns a list of all parts currently touching a specified BasePart.

By periodically calling :GetTouchingParts() (say: each heartbeat, or every 0.05 seconds) on the buttons in range of the player, you can check if any of the touching parts are associated with the player. If so, you can trigger the appropriate function while the player remains in contact with the button.

Hope this helps!

3 Likes

By checking if the button is within range you mean this?

RS.Heartbeat:Connect(function()
	for _, button in buttons do
		if (button.Position - HRP.Position).Magnitude >= minDistance then
			
		end
	end
end)

Yes, that’s exactly what I meant! Just one small detail:

You’re currently checking wether the distance from the player to the button is greater than the minimum distance:

This should actually be less, otherwise you’ll end up checking buttons that are far away instead of the nearby ones:

if (button.Position - HRP.Position).Magnitude <= minDistance then

And if you’re into the more technical side of things (I like to nerd out on security): if the client is making changes to data on the sever, it is curtial to also verify the player’s distance to the button on the server side. Otherwise, a malicious player could potentially activate buttons from a distance, bypassing any local checks.

But if that’s not your jam, no worries! Either way, yes, this is what I meant!

2 Likes

Thanks! This may work. Regarding how to detect if the button:GetTouchingParts() array has player parts, I would use a for loop for that?

local function HasPlayerParts(TouchingParts : {BasePart}) : boolean
	for _, part in TouchingParts do
		if part:FindFirstAncestor(char.Name) then
			return true
		end
	end
	
	return false
end

RS.Heartbeat:Connect(function()
	for _, button in buttons do
		if (button.Position - HRP.Position).Magnitude < minDistance then
			local touching = button:GetTouchingParts()
			
			if touching and HasPlayerParts(touching) then
				
			end
		end
	end
end)
1 Like

The code you’ve created looks great! This is exactly what I had in mind!

I’m still missing a bit of context regarding your specific use case, but from what I understand, you want a function to be called on each heartbeat. That’s exactly what your current setup does. The only thing left is to insert the function you’d like to call when the button is pressed inside this if statement:

Let me know if I’ve misunderstood your intent!

1 Like

This is exactly what I’m looking for. I’m working on it right now.

1 Like

I’ve stumbled onto a problem. GetTouchingParts doesn’t detect players’ part because they have CanCollide off; what should I use instead?

1 Like

I’ve made it to work. I’ve used worldRoot:GetPartsInPart() to check if there are any parts inside it. Thanks for the help, though!

EDIT: The new script is here:

local RS = game:GetService("RunService")
local CS = game:GetService("CollectionService")
local RpS = game:GetService("ReplicatedStorage")

local events = RpS.Events
local unreliables = events.Unreliables

local updateStats = unreliables.UpdateStats

local buttons : {BasePart} = CS:GetTagged("Button")

local char = script.Parent
local HRP : BasePart = char:WaitForChild("HumanoidRootPart")

local minDistance = 5

local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = (function()
	local parts = {}
	
	for _, basepart in char:GetDescendants() do
		if basepart:IsA("BasePart") then
			table.insert(parts, basepart)
		end
	end
	
	return parts
end)()

local connection : RBXScriptConnection

local function IsTouching(button : BasePart) : boolean
	return #workspace:GetPartsInPart(button, params) > 0
end

local function HandleNearButton(button : BasePart) : ()
	if connection.Connected then
		connection:Disconnect()
	end

	updateStats:FireServer(button)

	task.wait(.5)
end

local function FindNearButton() : ()
	for _, button in buttons do
		if (button.Position - HRP.Position).Magnitude < minDistance then
			while IsTouching(button) do
				print("Is touching "..button.Name)
				HandleNearButton(button)				
			end

			if not connection.Connected then
				connection = RS.Heartbeat:Connect(FindNearButton)
			end
		end
	end
end

connection = RS.Heartbeat:Connect(FindNearButton)
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.