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?
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?
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.
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.
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
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.
Alright, I’m understanding how it works but would it be better to use RunService’s Heartbeart or a while true do?
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.
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]
.
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
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.