Proximity Prompt Studio Beta

What is a ProximityPrompt?

ProximityPrompt is a method to prompt users and allow them to interact with an object in the 3D world, such as opening a door or picking up an item. Using this new feature you can:

  • Indicate what objects can be interacted with in the world
  • Display what action(s) can be taken on the object
  • Trigger that action(s) through user input
  • Display the correct input for all input types (keyboard/gamepad/touchscreen)
  • Use a tap to instantly trigger the action, or a hold to trigger the action over a period of time
  • Use the default UI provided or add your own custom UI

prompt

How do I use it?

First, enable the beta feature in Studio.

Enable the Beta Feature

To enable the beta feature:

  1. Launch Studio
  2. Select File, Beta Features .
  3. Check the box next to “Proximity Prompt Beta” and click Save .
  4. When prompted, restart Studio.

Create an object of type ProximityPrompt in the workspace as a child of a part, model, or attachment. Modify any properties as desired, such as ActionText and KeyboardKeyCode to adjust how the prompt looks or which button the user must press to interact with it.

In order to detect when the user interacts with the object, listen for the Triggered event on the ProximityPrompt in either a Script or LocalScript, as in this simple example:

workspace.Part.ProximityPrompt.Triggered:Connect(function(player)
    print("The user interacted with me!")
end)

Launch the game in Studio, and walk over to where the prompt was placed in the world. You will be able to see the UI, and you should be able to interact with it by pressing the E key by default (or whatever you changed KeyboardKeyCode to).

Use Cases

  • Tap button to interact - pick up an object, open a door, flip a switch, talk to NPC.

  • Hold button to trigger immediate action - require player to hold button for X seconds before picking a lock or opening a chest, which leaves the player exposed. HoldDuration is a useful property that, if non-zero, will require the player to hold the button down for a duration before the action is triggered. A progress bar animation indicates how long the button needs to be held.

  • Hold button for continuous action - player holds button to heal a teammate or water a plant over time. The action continues while the button is held and stops when the button is let go. “Hold” events are supported. Use the TriggerEnded event along with Triggered for this scenario. (Triggered and TriggerEnded events always come in pairs.)

Examples

In this example, a user must interact with a chair to sit in it. Paste this into a Script that is a child of a ProximityPrompt, which is itself a sibling of a Seat object named Seat.

local proximityPrompt = script.Parent
local seat = proximityPrompt.Parent.Seat
seat:GetPropertyChangedSignal("Occupant"):Connect(function()
    if seat.Occupant then
        proximityPrompt.Enabled = false
    else
        proximityPrompt.Enabled = true
    end
end)
proximityPrompt.Triggered:Connect(function(player)
    seat:Sit(player.Character.Humanoid)
end)

Customization

It is possible to fully customize the look and feel of the UI for ProximityPrompts. In order to do this, set the Style property to Custom. Then, listen to the PromptShown and PromptHidden events in a LocalScript, where you should create and tear down the UI. You may also use PromptButtonHoldBegan and PromptButtonHoldEnded in order to utilize the HoldDuration progress animation feature. Here is an example script that generates the UI similar to the default style, which you may use as a starting point. Paste this into a LocalScript

Show code
local UserInputService = game:GetService("UserInputService")
local ProximityPromptService = game:GetService("ProximityPromptService")
local TweenService = game:GetService("TweenService")
local TextService = game:GetService("TextService")
local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
while LocalPlayer == nil do
	Players.ChildAdded:wait()
	LocalPlayer = Players.LocalPlayer
end

local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")

local GamepadButtonImage = {
	[Enum.KeyCode.ButtonX] = "rbxasset://textures/ui/Controls/xboxX.png",
	[Enum.KeyCode.ButtonY] = "rbxasset://textures/ui/Controls/xboxY.png",
	[Enum.KeyCode.ButtonA] = "rbxasset://textures/ui/Controls/xboxA.png",
	[Enum.KeyCode.ButtonB] = "rbxasset://textures/ui/Controls/xboxB.png",
	[Enum.KeyCode.DPadLeft] = "rbxasset://textures/ui/Controls/dpadLeft.png",
	[Enum.KeyCode.DPadRight] = "rbxasset://textures/ui/Controls/dpadRight.png",
	[Enum.KeyCode.DPadUp] = "rbxasset://textures/ui/Controls/dpadUp.png",
	[Enum.KeyCode.DPadDown] = "rbxasset://textures/ui/Controls/dpadDown.png",
	[Enum.KeyCode.ButtonSelect] = "rbxasset://textures/ui/Controls/xboxmenu.png",
	[Enum.KeyCode.ButtonL1] = "rbxasset://textures/ui/Controls/xboxLS.png",
	[Enum.KeyCode.ButtonR1] = "rbxasset://textures/ui/Controls/xboxRS.png",
}

local KeyboardButtonImage = {
	[Enum.KeyCode.Backspace] = "rbxasset://textures/ui/Controls/backspace.png",
	[Enum.KeyCode.Return] = "rbxasset://textures/ui/Controls/return.png",
	[Enum.KeyCode.LeftShift] = "rbxasset://textures/ui/Controls/shift.png",
	[Enum.KeyCode.RightShift] = "rbxasset://textures/ui/Controls/shift.png",
	[Enum.KeyCode.Tab] = "rbxasset://textures/ui/Controls/tab.png",
}

local KeyboardButtonIconMapping = {
	["'"] = "rbxasset://textures/ui/Controls/apostrophe.png",
	[","] = "rbxasset://textures/ui/Controls/comma.png",
	["`"] = "rbxasset://textures/ui/Controls/graveaccent.png",
	["."] = "rbxasset://textures/ui/Controls/period.png",
	[" "] = "rbxasset://textures/ui/Controls/spacebar.png",
}

local KeyCodeToTextMapping = {
	[Enum.KeyCode.LeftControl] = "Ctrl",
	[Enum.KeyCode.RightControl] = "Ctrl",
	[Enum.KeyCode.LeftAlt] = "Alt",
	[Enum.KeyCode.RightAlt] = "Alt",
	[Enum.KeyCode.F1] = "F1",
	[Enum.KeyCode.F2] = "F2",
	[Enum.KeyCode.F3] = "F3",
	[Enum.KeyCode.F4] = "F4",
	[Enum.KeyCode.F5] = "F5",
	[Enum.KeyCode.F6] = "F6",
	[Enum.KeyCode.F7] = "F7",
	[Enum.KeyCode.F8] = "F8",
	[Enum.KeyCode.F9] = "F9",
	[Enum.KeyCode.F10] = "F10",
	[Enum.KeyCode.F11] = "F11",
	[Enum.KeyCode.F12] = "F12",
}

local function getScreenGui()
	local screenGui = PlayerGui:FindFirstChild("ProximityPrompts")
	if screenGui == nil then
		screenGui = Instance.new("ScreenGui")
		screenGui.Name = "ProximityPrompts"
		screenGui.ResetOnSpawn = false
		screenGui.Parent = PlayerGui
	end
	return screenGui
end

local function createProgressBarGradient(parent, leftSide)
	local frame = Instance.new("Frame")
	frame.Size = UDim2.fromScale(0.5, 1)
	frame.Position = UDim2.fromScale(leftSide and 0 or 0.5, 0)
	frame.BackgroundTransparency = 1
	frame.ClipsDescendants = true
	frame.Parent = parent

	local image = Instance.new("ImageLabel")
	image.BackgroundTransparency = 1
	image.Size = UDim2.fromScale(2, 1)
	image.Position = UDim2.fromScale(leftSide and 0 or -1, 0)
	image.Image = "rbxasset://textures/ui/Controls/RadialFill.png"
	image.Parent = frame

	local gradient = Instance.new("UIGradient")
	gradient.Transparency = NumberSequence.new {
		NumberSequenceKeypoint.new(0, 0),
		NumberSequenceKeypoint.new(.4999, 0),
		NumberSequenceKeypoint.new(.5, 1),
		NumberSequenceKeypoint.new(1, 1)
	}
	gradient.Rotation = leftSide and 180 or 0
	gradient.Parent = image

	return gradient
end

local function createCircularProgressBar()
	local bar = Instance.new("Frame")
	bar.Name = "CircularProgressBar"
	bar.Size = UDim2.fromOffset(58, 58)
	bar.AnchorPoint = Vector2.new(0.5, 0.5)
	bar.Position = UDim2.fromScale(0.5, 0.5)
	bar.BackgroundTransparency = 1

	local gradient1 = createProgressBarGradient(bar, true)
	local gradient2 = createProgressBarGradient(bar, false)

	local progress = Instance.new("NumberValue")
	progress.Name = "Progress"
	progress.Parent = bar
	progress.Changed:Connect(function(value)
		local angle = math.clamp(value * 360, 0, 360)
		gradient1.Rotation = math.clamp(angle, 180, 360)
		gradient2.Rotation = math.clamp(angle, 0, 180)
	end)

	return bar
end

local function createPrompt(prompt, inputType, gui)

	local tweensForButtonHoldBegin = {}
	local tweensForButtonHoldEnd = {}
	local tweensForFadeOut = {}
	local tweensForFadeIn = {}
	local tweenInfoInFullDuration = TweenInfo.new(prompt.HoldDuration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
	local tweenInfoOutHalfSecond = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	local tweenInfoFast = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	local tweenInfoQuick = TweenInfo.new(0.06, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)

	local promptUI = Instance.new("BillboardGui")
	promptUI.Name = "Prompt"
	promptUI.AlwaysOnTop = true

	local frame = Instance.new("Frame")
	frame.Size = UDim2.fromScale(0.5, 1)
	frame.BackgroundTransparency = 1
	frame.BackgroundColor3 = Color3.new(0.07, 0.07, 0.07)
	frame.Parent = promptUI

	local roundedCorner = Instance.new("UICorner")
	roundedCorner.Parent = frame

	local inputFrame = Instance.new("Frame")
	inputFrame.Name = "InputFrame"
	inputFrame.Size = UDim2.fromScale(1, 1)
	inputFrame.BackgroundTransparency = 1
	inputFrame.SizeConstraint = Enum.SizeConstraint.RelativeYY
	inputFrame.Parent = frame

	local resizeableInputFrame = Instance.new("Frame")
	resizeableInputFrame.Size = UDim2.fromScale(1, 1)
	resizeableInputFrame.Position = UDim2.fromScale(0.5, 0.5)
	resizeableInputFrame.AnchorPoint = Vector2.new(0.5, 0.5)
	resizeableInputFrame.BackgroundTransparency = 1
	resizeableInputFrame.Parent = inputFrame

	local inputFrameScaler = Instance.new("UIScale")
	inputFrameScaler.Parent = resizeableInputFrame

	local inputFrameScaleFactor = inputType == Enum.ProximityPromptInputType.Touch and 1.6 or 1.33
	table.insert(tweensForButtonHoldBegin, TweenService:Create(inputFrameScaler, tweenInfoFast, { Scale = inputFrameScaleFactor }))
	table.insert(tweensForButtonHoldEnd, TweenService:Create(inputFrameScaler, tweenInfoFast, { Scale = 1 }))

	local actionText = Instance.new("TextLabel")
	actionText.Name = "ActionText"
	actionText.Size = UDim2.fromScale(1, 1)
	actionText.Font = Enum.Font.GothamSemibold
	actionText.TextSize = 19
	actionText.BackgroundTransparency = 1
	actionText.TextTransparency = 1
	actionText.TextColor3 = Color3.new(1, 1, 1)
	actionText.TextXAlignment = Enum.TextXAlignment.Left
	actionText.Parent = frame
	table.insert(tweensForButtonHoldBegin, TweenService:Create(actionText, tweenInfoFast, { TextTransparency = 1 }))
	table.insert(tweensForButtonHoldEnd, TweenService:Create(actionText, tweenInfoFast, { TextTransparency = 0 }))
	table.insert(tweensForFadeOut, TweenService:Create(actionText, tweenInfoFast, { TextTransparency = 1 }))
	table.insert(tweensForFadeIn, TweenService:Create(actionText, tweenInfoFast, { TextTransparency = 0 }))

	local objectText = Instance.new("TextLabel")
	objectText.Name = "ObjectText"
	objectText.Size = UDim2.fromScale(1, 1)
	objectText.Font = Enum.Font.GothamSemibold
	objectText.TextSize = 14
	objectText.BackgroundTransparency = 1
	objectText.TextTransparency = 1
	objectText.TextColor3 = Color3.new(0.7, 0.7, 0.7)
	objectText.TextXAlignment = Enum.TextXAlignment.Left
	objectText.Parent = frame

	table.insert(tweensForButtonHoldBegin, TweenService:Create(objectText, tweenInfoFast, { TextTransparency = 1 }))
	table.insert(tweensForButtonHoldEnd, TweenService:Create(objectText, tweenInfoFast, { TextTransparency = 0 }))
	table.insert(tweensForFadeOut, TweenService:Create(objectText, tweenInfoFast, { TextTransparency = 1 }))
	table.insert(tweensForFadeIn, TweenService:Create(objectText, tweenInfoFast, { TextTransparency = 0 }))
	
	table.insert(tweensForButtonHoldBegin, TweenService:Create(frame, tweenInfoFast, { Size = UDim2.fromScale(0.5, 1), BackgroundTransparency = 1 }))
	table.insert(tweensForButtonHoldEnd, TweenService:Create(frame, tweenInfoFast, { Size = UDim2.fromScale(1, 1), BackgroundTransparency = 0.2 }))
	table.insert(tweensForFadeOut, TweenService:Create(frame, tweenInfoFast, { Size = UDim2.fromScale(0.5, 1), BackgroundTransparency = 1 }))
	table.insert(tweensForFadeIn, TweenService:Create(frame, tweenInfoFast, { Size = UDim2.fromScale(1, 1), BackgroundTransparency = 0.2 }))

	local roundFrame = Instance.new("Frame")
	roundFrame.Name = "RoundFrame"
	roundFrame.Size = UDim2.fromOffset(48, 48)

	roundFrame.AnchorPoint = Vector2.new(0.5, 0.5)
	roundFrame.Position = UDim2.fromScale(0.5, 0.5)
	roundFrame.BackgroundTransparency = 1
	roundFrame.Parent = resizeableInputFrame

	local roundedFrameCorner = Instance.new("UICorner")
	roundedFrameCorner.CornerRadius = UDim.new(0.5, 0)
	roundedFrameCorner.Parent = roundFrame

	table.insert(tweensForFadeOut, TweenService:Create(roundFrame, tweenInfoQuick, { BackgroundTransparency = 1 }))
	table.insert(tweensForFadeIn, TweenService:Create(roundFrame, tweenInfoQuick, { BackgroundTransparency = 0.5 }))

	if inputType == Enum.ProximityPromptInputType.Gamepad then
		if GamepadButtonImage[prompt.GamepadKeyCode] then
			local icon = Instance.new("ImageLabel")
			icon.Name = "ButtonImage"
			icon.AnchorPoint = Vector2.new(0.5, 0.5)
			icon.Size = UDim2.fromOffset(24, 24)
			icon.Position = UDim2.fromScale(0.5, 0.5)
			icon.BackgroundTransparency = 1
			icon.ImageTransparency = 1
			icon.Image = GamepadButtonImage[prompt.GamepadKeyCode]
			icon.Parent = resizeableInputFrame
			table.insert(tweensForFadeOut, TweenService:Create(icon, tweenInfoQuick, { ImageTransparency = 1 }))
			table.insert(tweensForFadeIn, TweenService:Create(icon, tweenInfoQuick, { ImageTransparency = 0 }))
		end
	elseif inputType == Enum.ProximityPromptInputType.Touch then
		local buttonImage = Instance.new("ImageLabel")
		buttonImage.Name = "ButtonImage"
		buttonImage.BackgroundTransparency = 1
		buttonImage.ImageTransparency = 1
		buttonImage.Size = UDim2.fromOffset(25, 31)
		buttonImage.AnchorPoint = Vector2.new(0.5, 0.5)
		buttonImage.Position = UDim2.fromScale(0.5, 0.5)
		buttonImage.Image = "rbxasset://textures/ui/Controls/TouchTapIcon.png"
		buttonImage.Parent = resizeableInputFrame

		table.insert(tweensForFadeOut, TweenService:Create(buttonImage, tweenInfoQuick, { ImageTransparency = 1 }))
		table.insert(tweensForFadeIn, TweenService:Create(buttonImage, tweenInfoQuick, { ImageTransparency = 0 }))
	else
		local buttonImage = Instance.new("ImageLabel")
		buttonImage.Name = "ButtonImage"
		buttonImage.BackgroundTransparency = 1
		buttonImage.ImageTransparency = 1
		buttonImage.Size = UDim2.fromOffset(28, 30)
		buttonImage.AnchorPoint = Vector2.new(0.5, 0.5)
		buttonImage.Position = UDim2.fromScale(0.5, 0.5)
		buttonImage.Image = "rbxasset://textures/ui/Controls/key_single.png"
		buttonImage.Parent = resizeableInputFrame
		table.insert(tweensForFadeOut, TweenService:Create(buttonImage, tweenInfoQuick, { ImageTransparency = 1 }))
		table.insert(tweensForFadeIn, TweenService:Create(buttonImage, tweenInfoQuick, { ImageTransparency = 0 }))

		local buttonTextString = UserInputService:GetStringForKeyCode(prompt.KeyboardKeyCode)

		local buttonTextImage = KeyboardButtonImage[prompt.KeyboardKeyCode]
		if buttonTextImage == nil then
			buttonTextImage = KeyboardButtonIconMapping[buttonTextString]
		end

		if buttonTextImage == nil then
			local keyCodeMappedText = KeyCodeToTextMapping[prompt.KeyboardKeyCode]
			if keyCodeMappedText then
				buttonTextString = keyCodeMappedText
			end
		end

		if buttonTextImage then
			local icon = Instance.new("ImageLabel")
			icon.Name = "ButtonImage"
			icon.AnchorPoint = Vector2.new(0.5, 0.5)
			icon.Size = UDim2.fromOffset(36, 36)
			icon.Position = UDim2.fromScale(0.5, 0.5)
			icon.BackgroundTransparency = 1
			icon.ImageTransparency = 1
			icon.Image = buttonTextImage
			icon.Parent = resizeableInputFrame
			table.insert(tweensForFadeOut, TweenService:Create(icon, tweenInfoQuick, { ImageTransparency = 1 }))
			table.insert(tweensForFadeIn, TweenService:Create(icon, tweenInfoQuick, { ImageTransparency = 0 }))
		elseif buttonTextString ~= nil and buttonTextString ~= '' then
			local buttonText = Instance.new("TextLabel")
			buttonText.Name = "ButtonText"
			buttonText.Position = UDim2.fromOffset(0, -1)
			buttonText.Size = UDim2.fromScale(1, 1)
			buttonText.Font = Enum.Font.GothamSemibold
			buttonText.TextSize = 14
			if string.len(buttonTextString) > 2 then
				buttonText.TextSize = 12
			end
			buttonText.BackgroundTransparency = 1
			buttonText.TextTransparency = 1
			buttonText.TextColor3 = Color3.new(1, 1, 1)
			buttonText.TextXAlignment = Enum.TextXAlignment.Center
			buttonText.Text = buttonTextString
			buttonText.Parent = resizeableInputFrame
			table.insert(tweensForFadeOut, TweenService:Create(buttonText, tweenInfoQuick, { TextTransparency = 1 }))
			table.insert(tweensForFadeIn, TweenService:Create(buttonText, tweenInfoQuick, { TextTransparency = 0 }))
		else
			error("ProximityPrompt '" .. prompt.Name .. "' has an unsupported keycode for rendering UI: " .. tostring(prompt.KeyboardKeyCode))
		end
	end

	if inputType == Enum.ProximityPromptInputType.Touch or prompt.ClickablePrompt then
		local button = Instance.new("TextButton")
		button.BackgroundTransparency = 1
		button.TextTransparency = 1
		button.Size = UDim2.fromScale(1, 1)
		button.Parent = promptUI

		local buttonDown = false

		button.InputBegan:Connect(function(input)
			if (input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1) and
				input.UserInputState ~= Enum.UserInputState.Change then
				prompt:InputHoldBegin()
				buttonDown = true
			end
		end)
		button.InputEnded:Connect(function(input)
			if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
				if buttonDown then
					buttonDown = false
					prompt:InputHoldEnd()
				end
			end
		end)

		promptUI.Active = true
	end

	if prompt.HoldDuration > 0 then
		local circleBar = createCircularProgressBar()
		circleBar.Parent = resizeableInputFrame
		table.insert(tweensForButtonHoldBegin, TweenService:Create(circleBar.Progress, tweenInfoInFullDuration, { Value = 1 }))
		table.insert(tweensForButtonHoldEnd, TweenService:Create(circleBar.Progress, tweenInfoOutHalfSecond, { Value = 0 }))
	end

	local holdBeganConnection
	local holdEndedConnection
	local triggeredConnection
	local triggerEndedConnection
	
	if prompt.HoldDuration > 0 then
		holdBeganConnection = prompt.PromptButtonHoldBegan:Connect(function()
			for _, tween in ipairs(tweensForButtonHoldBegin) do
				tween:Play()
			end
		end)
	
		holdEndedConnection = prompt.PromptButtonHoldEnded:Connect(function()
			for _, tween in ipairs(tweensForButtonHoldEnd) do
				tween:Play()
			end
		end)
	end
	
	triggeredConnection = prompt.Triggered:Connect(function()
		for _, tween in ipairs(tweensForFadeOut) do
			tween:Play()
		end
	end)
	
	triggerEndedConnection = prompt.TriggerEnded:Connect(function()
		for _, tween in ipairs(tweensForFadeIn) do
			tween:Play()
		end
	end)
	
	local function updateUIFromPrompt()
		-- todo: Use AutomaticSize instead of GetTextSize when that feature becomes available
		local actionTextSize = TextService:GetTextSize(prompt.ActionText, 19, Enum.Font.GothamSemibold, Vector2.new(1000, 1000))
		local objectTextSize = TextService:GetTextSize(prompt.ObjectText, 14, Enum.Font.GothamSemibold, Vector2.new(1000, 1000))
		local maxTextWidth = math.max(actionTextSize.X, objectTextSize.X)
		local promptHeight = 72
		local promptWidth = 72
		local textPaddingLeft = 72

		if (prompt.ActionText ~= nil and prompt.ActionText ~= '') or
			(prompt.ObjectText ~= nil and prompt.ObjectText ~= '') then
			promptWidth = maxTextWidth + textPaddingLeft + 24
		end
	
		local actionTextYOffset = 0
		if prompt.ObjectText ~= nil and prompt.ObjectText ~= '' then
			actionTextYOffset = 9
		end
		actionText.Position = UDim2.new(0.5, textPaddingLeft - promptWidth/2, 0, actionTextYOffset)
		objectText.Position = UDim2.new(0.5, textPaddingLeft - promptWidth/2, 0, -10)
	
		actionText.Text = prompt.ActionText
		objectText.Text = prompt.ObjectText
		actionText.AutoLocalize = prompt.AutoLocalize
		actionText.RootLocalizationTable = prompt.RootLocalizationTable
		
		objectText.AutoLocalize = prompt.AutoLocalize
		objectText.RootLocalizationTable = prompt.RootLocalizationTable

		promptUI.Size = UDim2.fromOffset(promptWidth, promptHeight)
		promptUI.SizeOffset = Vector2.new(prompt.UIOffset.X / promptUI.Size.Width.Offset, prompt.UIOffset.Y / promptUI.Size.Height.Offset)
	end
	
	local changedConnection = prompt.Changed:Connect(updateUIFromPrompt)
	updateUIFromPrompt()
	
	promptUI.Adornee = prompt.Parent
	promptUI.Parent = gui

	for _, tween in ipairs(tweensForFadeIn) do
		tween:Play()
	end

	local function cleanup()
		if holdBeganConnection then
			holdBeganConnection:Disconnect()
		end
	
		if holdEndedConnection then
			holdEndedConnection:Disconnect()
		end
	
		triggeredConnection:Disconnect()
		triggerEndedConnection:Disconnect()
		changedConnection:Disconnect()

		for _, tween in ipairs(tweensForFadeOut) do
			tween:Play()
		end

		wait(0.2)
	
		promptUI.Parent = nil
	end
	
	return cleanup
end

local function onLoad()

	ProximityPromptService.PromptShown:Connect(function(prompt, inputType)
		if prompt.Style == Enum.ProximityPromptStyle.Default then
			return
		end

		local gui = getScreenGui()

		local cleanupFunction = createPrompt(prompt, inputType, gui)

		prompt.PromptHidden:Wait()

		cleanupFunction()
	end)
end

onLoad()

Is there anything else I should know about?

  • Cross-platform support is built in and fully compatible with touchscreens and gamepad input devices; on mobile, the user must tap on the prompt in order to activate it

  • ProximityPromptService is an associated service that you can use to interact with this feature in a global way. It may be more convenient to listen to events on this service instead of individual ProximityPrompt objects.

  • To support multiple actions on one object, you can create multiple ProximityPrompts and offset them. If using the default UI, set the UIOffset property of the second on one to 0,72

  • NOTE: This feature is in a beta stage and is subject to some changes

THE API

PROPERTIES

Property Description
MaxActivationDistance The maximum distance a Player’s character can be from the ProximityPrompt for the prompt to appear.
ActionText The action text shown to the user.
ObjectText The optional object name text shown to the user.
KeyboardKeyCode The key the player should press to trigger the prompt.
GamepadKeyCode The gamepad button the player should press to trigger the prompt.
Enabled Whether or not this prompt should be shown.
HoldDuration The user must hold the button down for this duration to trigger the prompt.
Style When set to Custom, no default UI will be provided.
UIOffset A pixel offset applied to the UI.
RequiresLineOfSight If true, this prompt will only be shown if there is a clear path from the camera to the object. The parent part or model of the prompt will be excluded from this check.
ClickablePrompt When false, tapping the prompt does not activate it except on mobile.
Exclusivity Used to customize which prompts can be shown at the same time. There are 3 options: OneGlobally - Only one prompt will be shown with this setting, OnePerButton - One prompt will be shown per input keycode, AlwaysShow - This prompt will always show when in range and visible.

EVENTS

Event Description
Triggered(player) Event triggers when prompt key/button is pressed, or after a specified amount of time holding the button, if HoldDuration is used.
TriggerEnded(player) Event triggers when key/button is released, for longer events where the user is required to hold down the button (e.g. heal another player over time.)

CUSTOMIZATION EVENTS

Event Description
PromptShown(inputType) Will trigger in local scripts when the prompt becomes visible.
PromptHidden() Will trigger in local scripts when the prompt will be hidden.
PromptButtonHoldBegan(player) Will trigger while the user begins holding down the button on a prompt with a non-zero HoldDuration. This can be used to animate a progress bar.
PromptButtonHoldEnded(player) Will trigger while the user ends holding down the button on a prompt with a non-zero HoldDuration. This can be used to animate a progress bar.

FUNCTIONS

Function Description
InputHoldBegin This should be used by developers who wish to customize the prompt and trigger it from a GUI button press when handling input from a touch device. This signals that the user began pressing the UI button.
InputHoldEnd A counterpoint to InputHoldBegin, this signals that the user ended pressing the GUI button.

Service: ProximityPromptService

This service exists to allow developers to interact with prompts in a global way for customization.

PROPERTIES:

Property Description
Enabled When false, no prompts will be shown.
MaxPromptsVisible The maximum number of ProximityPrompts that will be shown to the user.

EVENTS

Event Description
PromptShown(prompt, inputType) Will trigger in local scripts when a prompt becomes visible.
PromptHidden(prompt) Will trigger in local scripts when a prompt will be hidden.
PromptButtonHoldBegan(prompt, player) Will trigger while the user begins holding down the button on a prompt with a non-zero HoldDuration. This can be used to animate a progress bar.
PromptButtonHoldEnded(prompt, player) Will trigger while the user ends holding down the button on a prompt with a non-zero HoldDuration. This can be used to animate a progress bar.
PromptTriggered(prompt, player) Will trigger when the user interacts with this prompt.
PromptTriggerEnded(prompt, player) Will trigger when the user stops holding down the button while triggering a prompt, intended to allow interactions which require the user to hold a button while something happens in-game.

Known Issues

  • Automatic localization is not yet supported on ActionText or ObjectText
  • Changing some properties such as ActionText at runtime while a prompt is visible will not update right away
  • Prompts can be triggered while the Roblox menu is open
  • Errors will occur if a ProximityPrompt is added as a child of the workspace or a model with no PrimaryPart
  • On mobile, dragging onto a prompt will trigger it
  • In first person, hovering over a prompt makes the mouse stuck
  • Proximity prompts are functional when placed in ReplicatedStorage, Lighting, StarterPlayerScripts, etc.
  • When inserting a ProximityPrompt in Studio, it is necessary to uncheck “show only recommended objects”
826 Likes

13 posts were split to a new topic: Feedback - Like Instead of Reply

Is there a link to the API somewhere on the developer website? (https://developer.roblox.com)

26 Likes

This is a great feature to have built into Roblox! It will be so much easier to create interaction prompts.

I did find one issue while testing the Beta feature though. Whenever you insert a ProximityPrompt into just workspace itself (This also happens whenever you put it into a model without a PrimaryPart) it just spams this error on loop until it is parented to something else.

38 Likes

I really enjoy that you added easy support for Xbox and Mobile on this. However, when you are on mobile and dragging your finger around, you don’t have to click the prompt in order to for it to activate. I think if there was a property like DragActivate, or something along those lines it would be nice. If there is a 0 second prompt, a mobile user can easily accidentally activate something.

16 Likes

Great catch! Thanks for trying out the beta. We will keep track of this issue.

22 Likes

Hey there, I was curious how this behaves with StreamingEnabled? Does a Proximity Prompt object get streamed out & the connection gets cut? Or what is the expecting behavior?

30 Likes

Is the prompt shown when the character is near the object or when the Player’s camera is near it?

7 Likes

A great update! But I have a question, instead of just creating the whole UI through a script, why not like just create a UI and then maybe have like a function that used that UI as the prompt? It would be very space-consuming when we are creating UIs through a script. Hope this feature added inside Roblox!

7 Likes

Quick question, will there be possible gui customization for these? The gui looks nice and all, but it would be nice to be able to customize it to whatever I’d like. Other than that, great feature!

8 Likes

The post had just said it, you can customize it through a script.

6 Likes

You already can - set Style to Custom.

8 Likes

Thanks for trying out the beta!
By default, the character’s position is used. They won’t be able to interact with it from far away. :grinning: If no character exists, the camera’s focus position is used.

16 Likes

pog
979b9805a691f507c4a7cc035c7266d0867c28cd

As always, the UI is 10/10.

@M_caw I love your idea. We could have an animation for when the prompt is shown, and when the prompt is hidden.


Property ShownAnimation: The animation for the UI when the prompt is shown.
Property HiddenAnimation: The animation for the UI when the prompt is hidden.

^^ this would be how it is like.


But wait, could we do this with the PromptShown/PromptHidden event?

21 Likes

Thanks for testing this out on mobile so quickly! We will look into this issue. We probably don’t want dragging to activate it.

12 Likes

really great update,but i personally would add a function called UseProximityGUI(instance UI). Basically instead of style, you can use the UI you have made. dont get me wrong, built in is cool,but ig some people prefers their GUI.

10 Likes

This seems a lot easier than messing around with contexts and actions!

Another cool feature. Good job Roblox! :smiley:

My only concern is that it seems customizing this is a bit tricky; looks like you need to create the UI within a script, as opposed to setting it within the instance? I suppose you could create a clone of a fully designed instance and use that in the script.

Actually, the more I read into it, this seems fairly complicated to use. My morning coffee hasn’t reached my head yet, though; I guess i’ll need to try use them and find out.

The Service version of this seems extremely useful, circumventing some of the confusions I had around customizing the object instances.

Overall, very exciting!

8 Likes

i cant insert it

how do i insert it

6 Likes

What do you mean insert it? Please specify clearer.

5 Likes

how do i activate this in a part or something

3 Likes