Part Touched Event

I’m not too sure of the best and accurate approach for when a player touches a specific part. I would like it detect the player, but forgot the player once they’re actually done touching the part. I looked at how Touched() and TouchEnded() work, but they both seem to trigger at the same time. Anyway I could get this to work?

4 Likes

Would you like:
a) The operation to run once, when the player touches the part, or…
b) The operation to continue until the player stops touching the part?

1 Like

Mainly B, but it’s more of a detector. So let’s say Player A touched the part, and I wanted to keep that value stored in the script, and use it somewhere else. I want the value to stay stored until the player stops touching the defined part.

1 Like

I would not use this method to determine if a player is touching a part if you need to know when they stop touching it. It’s far more work than it seems and it almost never works well. Unfortunately the .Touched event seems to execute EVERY time an object moves on an object listening for touches.

For example, if you have a sphere with CanCollide set to false and you walk inside of that sphere, every time any of your character’s parts move it will trigger the .Touched event. This is really a pain to deal with since it causes your .TouchEnded event to also fire. It’s just a terrible interface and isn’t worth using. I would recommend a distance check instead if you need to know when the player stops touching the part.

1 Like

The original piece I wrote didn’t include TouchEnded. I was seeing how TouchEnded worked, if it was the end call of Touched() or if it was the end of an object touching the defined part. I had used a “timeout” detector to remove the player after said time since when a Part is Touching another Part. The Touched() event is constantly being called. It seems inefficient as there was a delay when the player was done touching the part.

This is how I orignally did it, but like I said, there is a delay when the player is done touching the part. I didn’t want to remove the wait() to prevent any LUA lag issues.
How this function is called: CreatePad(string, string, part)

function CreatePad(Side, Button, Part)
	local Timeout = 0
	local TouchDebounce = false
	local function TouchEvent(Object)
		
		local Player = PlayerService:GetPlayerFromCharacter(Object.Parent)
		if not Player then return end
		
		if Part.Transparency >= 1 then return end
		if not TouchDebounce then TouchDebounce = true else return end
		
		if Competitors[Side][Button] == Player then
			Timeout = 0.01
			TouchDebounce = false
			return
		elseif typeof(Competitors[Side][Button]) ~= "nil" then
			return
		end
		
		local function Timer()
			repeat
				wait()
				Timeout = Timeout - 0.01
				if Timeout <= 0 then break end
			until not Player or Timeout <= 0
			Competitors[Side][Button] = nil
			Timeout = 0
			return
		end
		
		Competitors[Side][Button] = Player
		Timeout = 0.01
		spawn(Timer)
		
		TouchDebounce = false
	end
	
	Part.Touched:Connect(TouchEvent)
end
1 Like

I think using Region3 is your best bet here to be honest. Workspace | Documentation - Roblox Creator Hub

Just white list player characters and I think you’ll find this is a far more simple solution. You could even trigger the region checks on touch to add efficiency.

4 Likes

Alright, I’m understanding how it works but would it be better to use RunService’s Heartbeart or a while true do?

1 Like

Heartbeat would be pretty over kill as it runs ~60 times a second. I would think a simple while loop with a short wait is fine. Since this will only be running while the player is “touching” I doubt it would cause a significant performance impact.

I really respect your effort to try to use the event driven method. It’s really a shame that the API doesn’t do a better job of finding when a touch starts and ends. I imagine this is an attempt to increase performance on the physics step since this would require it to remember what parts are touching on each step and that would be far less computational and memory efficient.

1 Like

Yeah, I really wish it was better efficient as most of the time, a lot of things need to use Touched(). It should have a table that’s stored somewhere that allows players to obtain what’s CURRENTLY touching the defined part. The only down-side to this method is I have to learn about Region3, as this will be my first time messing with it haha. As I always say, it’s better to learn something then nothing.

There is a way to get all of the touching parts from BasePart | Documentation - Roblox Creator Hub the downside is that you still can’t rely on TouchEnded being accurate.

If you do use Region3, your code may look similar to this:

local workspaceService = game:GetService("Workspace")
local runService = game:GetService("RunService")

local function clear(t)
    for k in pairs(t) do
        t[k] = nil
    end
end

while true do
    runService.Heartbeat:Wait()
    -- get primary parts
    local primaryPartPlayers = {}
    local primaryParts = {}
    for _, player in ipairs(playersService:GetPlayers()) do
        local character = player.Character
        if character then
            local primaryPart = character.PrimaryPart
            primaryPartPlayers[primaryPart] = player
            table.insert(primaryParts, primaryPart)
        end
    end
    -- update players in regions
    for region, players in pairs(regionPlayers) do
        -- clear old players
        clear(players)
        -- get new players
        local parts = workspaceService:FindPartsInRegion3WithWhiteList(region, primaryParts)
        for _, part in ipairs(parts) do
            players[primaryPartPlayers[part]] = true
        end
    end
end

regionPlayers is a dictionary with keys as Region3s and values as a dictionary of players (with keys as players and values as true). You’d initially set it up with just the regions, like:

local regionPlayers = {
    [region1] = {},
    [region2] = {},
    [regionN] = {},
}

You can access the current players in a specific region with regionPlayers[region].

3 Likes

I’m attempting to use Region3, but I’m not too sure if this is exactly correct. If I print the Objects table for the Region3 function, it’s not printing anything

function CreatePad(Side, Button, Part, Hitbox)
	local VectorLow = Vector3.new(Hitbox.Position.X - (Hitbox.Size.X/2),Hitbox.Position.Y - (Hitbox.Size.Y/2),Hitbox.Position.Z - (Hitbox.Size.Z/2))
	local VectorHigh = Vector3.new(Hitbox.Position.X + (Hitbox.Size.X/2),Hitbox.Position.Y + (Hitbox.Size.Y/2),Hitbox.Position.Z + (Hitbox.Size.Z/2))
	local Region = Region3.new(VectorLow, VectorLow)

	local function Detection()
		while true do
			RunService.Heartbeat:Wait()
			local Characters = {}
			for _,Plr in pairs(PlayerService:GetPlayers()) do
				if Plr.Character then
					table.insert(Characters, Plr.Character)
				end
			end
			
			local Objects = workspace:FindPartsInRegion3WithWhiteList(Region, Characters, 30)
			local Player
			for _,Object in pairs(Objects) do
				Player = PlayerService:GetPlayerFromCharacter(Object.Parent)
				if Player then break end
			end
			
			--// We have the player, woohoo!
			
		end
	end
	
	spawn(Detection)
end
1 Like

This is about as efficient as I can think to make it without implementing a nice custom event structure.

local partToListenForTouchOn = workspace.SOMEPART;

local function playersInRegion(minVec, maxVec)
	local playersInRegion = {};
	
	local characters = {};
	local charHash = {};
	for i, player in pairs(game:GetService('Players'):GetChildren()) do
		if (player.Character) then
			table.insert(characters, player.Character);
			charHash[player.Character] = player; -- For quick player lookup.
		end
	end
	
	local parts = workspace:FindPartsInRegion3WithWhiteList(Region3.new(minVec, maxVec), characters, 1);
	for i, part in pairs(parts) do
		if (charHash[part.Parent]) then
			table.insert(playersInRegion, charHash[part.Parent]);
		end
	end
	
	return playersInRegion;
end


local function listenForPlayerTouch(touchPart, onTouch, onTouchEnd)
	local touchListener;
	
	touchListener = touchPart.Touched:Connect(function(toucher)
		local touchingPlayer = nil;
		
		for i, player in pairs(game:GetService('Players'):GetChildren()) do
			if (toucher.Parent == player.Character) then
				touchingPlayer = player;
				break;
			end
		end
		
		if (touchingPlayer) then
      onTouch(touchingPlayer);

			touchListener:Disconnect();
			while wait() do
				local touchingPlayers = playersInRegion(
					touchPart.Position - touchPart.Size,
					touchPart.Position + touchPart.Size + Vector3.new(0,1,0)
				);
				
				if (#touchingPlayers == 0) then
					listenForPlayerTouch(touchPart);

          onTouchEnd(touchingPlayer);
					
					break;
				end
			end
		end
	end);
end

Then you could use this like so:

listenForPlayerTouch(partToListenForTouchOn,
    (function(player) print('Touch begin', player) end),
    (function() print('Touch End') end)
);

Of course you can adjust that wait() to your liking. I don’t think it will be too tough on your game in its current state though.

UPDATE: I’ve updated it to use callback functions when the touch begins and the touch ends. This should be plenty flexible to suit your needs. Let me know if anything doesn’t make sense and I’ll try to elaborate.

7 Likes