Wait until player is no longer touching tagged part?

trying to make a system where upon touching a teleport trigger (which are tagged as “teleportTrigger”), they’re teleported to a part (referenced as an objectvalue called “teleportTo”). since it’s likely that the teleportTo part is also a teleport trigger, i want the game to wait until the player is no longer touching ANY part with the teleportTrigger tag before allowing the player to teleport again.

here’s my code (it currently rapidly teleports the player between parts, which is not what i want):

local collectServ = game:GetService("CollectionService");
local player = game:GetService("Players").LocalPlayer;

local teleportConnections = {};
local isTouchingTrigger = script:WaitForChild("isTouchingTrigger");

local function checkTriggerParts()
	local playerParts = {};
	local params = OverlapParams.new();
	params.FilterDescendantsInstances = {player.Character};
	params.FilterType = Enum.RaycastFilterType.Include;
	
	for i, triggerPart in pairs(collectServ:GetTagged("teleportTrigger")) do
		local touchingPlayerParts = game.Workspace:GetPartsInPart(triggerPart, params);
		for i, v in pairs(touchingPlayerParts) do
			table.insert(playerParts, v);
		end
	end
	print(#playerParts);
	if #playerParts == 0 then -- if the player is NOT touching any teleport
		isTouchingTrigger.Value = false;
	end
end

local function handleTrigger(hitPart, triggerPart)
	local teleportTo = triggerPart:FindFirstChild("teleportTo"); -- ObjectValue that points to "recipient" part
	if isTouchingTrigger.Value == false and teleportTo.Value then -- if you aren't already touching a triggerPart and there is a destination part
		if hitPart.Parent.Name == player.Name and player.Character.HumanoidRootPart then -- if the touching part is the player and we have a rootPart
			isTouchingTrigger.Value = true; -- we are touching a trigger
			local rootPart = player.Character.HumanoidRootPart;
			rootPart.CFrame = teleportTo.Value.CFrame + Vector3.new(0, (rootPart.Size.Y*2) + (triggerPart.Size.Y/2), 0); -- set the player's position to the teleportTo
			triggerPart.TouchEnded:Wait();
			checkTriggerParts()
		end
	end
end

collectServ:GetInstanceAddedSignal("teleportTrigger"):Connect(function(triggerPart)
	teleportConnections[triggerPart] = triggerPart.Touched:Connect(function(hitPart)
		handleTrigger(hitPart, triggerPart);
	end);
end)
collectServ:GetInstanceRemovedSignal("teleportTrigger"):Connect(function(triggerPart)
	teleportConnections[triggerPart]:Disconnect();
end)

for i, triggerPart in pairs(collectServ:GetTagged("teleportTrigger")) do
	teleportConnections[triggerPart] = triggerPart.Touched:Connect(function(hitPart)
		handleTrigger(hitPart, triggerPart);
	end);
end

teleportTo is an object value inside the teleportTrigger part, for reference

I worked up a bit of a loose solution, but it works nonetheless. Probably may need a bit of adjustment with how you want the delay to work.
In summary, it waits for the (what was checkTriggerParts()) to be false before letting them teleport them. I also added a delay for letting them stand off it so they can walk away safely.

local collectServ = game:GetService("CollectionService");
local player = game:GetService("Players").LocalPlayer;

local teleportConnections = {};
local isTouchingTrigger = script:WaitForChild("isTouchingTrigger");

-- Slightly altered checkTriggerParts, returns if there's more than 1 part being touched
local function isTouchingTeleport()
	local playerParts = {};
	local params = OverlapParams.new();
	params.FilterDescendantsInstances = {player.Character};
	params.FilterType = Enum.RaycastFilterType.Include;
	
	for i, triggerPart in pairs(collectServ:GetTagged("teleportTrigger")) do
		local touchingPlayerParts = game.Workspace:GetPartsInPart(triggerPart, params);
		for i, v in pairs(touchingPlayerParts) do
			table.insert(playerParts, v);
		end
	end
	--print(#playerParts);
	return #playerParts >= 1
end

-- Teleports the player's charcter, parital debounce for
-- letting the player leave the teleporter
local function handleTrigger(hitPart, triggerPart)
	local teleportTo = triggerPart:FindFirstChild("teleportTo"); -- ObjectValue that points to "recipient" part
	if isTouchingTrigger.Value == false and teleportTo.Value then -- if you aren't already touching a triggerPart and there is a destination part
		if hitPart.Parent.Name == player.Name and player.Character.HumanoidRootPart then -- if the touching part is the player and we have a rootPart
			isTouchingTrigger.Value = true; -- we are touching a trigger
			local rootPart = player.Character.HumanoidRootPart;
			rootPart.CFrame = teleportTo.Value.CFrame + Vector3.new(0, (rootPart.Size.Y*2) + (triggerPart.Size.Y/2), 0); -- set the player's position to the teleportTo
			
			-- Partial delay for when they land
			delay(0.2, function()
				repeat task.wait() until not isTouchingTeleport()
				
				-- Partial delay to let them fully get off
				task.wait(0.2)
				isTouchingTrigger.Value = false;
			end)
		end
	end
end

collectServ:GetInstanceAddedSignal("teleportTrigger"):Connect(function(triggerPart)
	teleportConnections[triggerPart] = triggerPart.Touched:Connect(function(hitPart)
		handleTrigger(hitPart, triggerPart);
	end);
end)
collectServ:GetInstanceRemovedSignal("teleportTrigger"):Connect(function(triggerPart)
	teleportConnections[triggerPart]:Disconnect();
end)

for i, triggerPart in pairs(collectServ:GetTagged("teleportTrigger")) do
	teleportConnections[triggerPart] = triggerPart.Touched:Connect(function(hitPart)
		handleTrigger(hitPart, triggerPart);
	end);
end
1 Like

Hi, I also made an example:
Teleporter.rbxl (53.9 KB)

local alreadyTeleported = {}

function isPlayer(model)
	if model then
		local player = game:GetService("Players"):GetPlayerFromCharacter(model)
		if player then
			return player
		end
	end
end

local teleporterA = workspace.Teleporters.A
local teleporterB = workspace.Teleporters.B

teleporterA.Touched:Connect(function(hitPart)
	local model = hitPart:FindFirstAncestorOfClass("Model")
	local player = isPlayer(model)
	if player then
		if alreadyTeleported[player] then -- prevent player from teleporting if teleported from another part
			return
		end
		alreadyTeleported[player] = true
		
		model:PivotTo(teleporterB.CFrame)
		
		local checkParts:RBXScriptConnection
		checkParts = game:GetService("RunService").Heartbeat:Connect(function()
			local touchedParts = workspace:GetPartsInPart(teleporterB)
			
			for _,touchPart:BasePart in pairs(touchedParts) do
				local model = touchPart:FindFirstAncestorOfClass("Model")
				local playerInB = isPlayer(model)
				if playerInB == player then
					return -- return if has player
				end
			end
			checkParts:Disconnect() -- made it here cuz no return
			alreadyTeleported[player] = false
		end)
	end
end)

teleporterB.Touched:Connect(function(hitPart)
	local model = hitPart:FindFirstAncestorOfClass("Model")
	local player = isPlayer(model)
	if player then
		if alreadyTeleported[player] then -- prevent player from teleporting if teleported from another part
			return
		end
		alreadyTeleported[player] = true

		model:PivotTo(teleporterA.CFrame)

		local checkParts:RBXScriptConnection
		checkParts = game:GetService("RunService").Heartbeat:Connect(function()
			for _,touchPart:BasePart in pairs(workspace:GetPartsInPart(teleporterA)) do
				local model = touchPart:FindFirstAncestorOfClass("Model")
				local playerInA = isPlayer(model)
				if playerInA == player then
					return -- return if has player
				end
			end
			checkParts:Disconnect() -- made it here cuz no return
			alreadyTeleported[player] = false
		end)
	end
end)
1 Like

You can also make a function for reusability:

function makeTeleporter(teleporterPart, destinationPart)
	teleporterPart.Touched:Connect(function(hitPart)
		local model = hitPart:FindFirstAncestorOfClass("Model")
		local player = isPlayer(model)
		if player then
			if alreadyTeleported[player] then -- prevent player from teleporting if teleported from another part
				return
			end
			alreadyTeleported[player] = true

			model:PivotTo(destinationPart.CFrame) -- teleport player to destination

			local checkParts:RBXScriptConnection
			checkParts = game:GetService("RunService").Heartbeat:Connect(function()
				local touchedParts = workspace:GetPartsInPart(destinationPart)

				for _,touchPart:BasePart in pairs(touchedParts) do
					local model = touchPart:FindFirstAncestorOfClass("Model")
					local playerInB = isPlayer(model)
					if playerInB == player then
						return -- return if has the same player still inside destination part
					end
				end
				checkParts:Disconnect() -- made it here cuz no return
				alreadyTeleported[player] = false
			end)
		end
	end)
end

makeTeleporter(teleporterA, teleporterB)
makeTeleporter(teleporterB, teleporterA)

Feel free to add your own features such as Delay, and OverlapParams for workspace:GetPartsInPart()

this is great!! i was today years old when i found out about delay()


@DudeW0rld also i appreciate your hand in trying out this too :] looks like you went for a server script approach, meanwhile i was going for a localscript approach. i'll keep this in mind in case anything happens!!

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