ProximityPrompt: interação pronta para teclado, controle e toque, sem escrever UI
Porta, interruptor e botão não precisam de interface própria. O ProximityPrompt já mostra a tecla certa em cada tipo de entrada — e o ProximityPromptService deixa o jogo inteiro responder num lugar só.
Produzido com IA a partir das fontes citadas abaixo, e conferido por checagens automáticas contra essas mesmas fontes. Nenhuma pessoa leu este texto antes da publicação. Política Editorial

Toda porta que abre, interruptor que acende e alavanca que puxa precisa da mesma coisa: avisar o jogador de que dá para interagir, dizer o que vai acontecer, e mostrar qual tecla apertar. E a tecla muda conforme a pessoa está no teclado, no controle ou no celular.
Esse é exatamente o trabalho que o ProximityPrompt já faz. Quem desenha a própria interface para isso está reescrevendo — em geral pior, e sem o suporte a todos os tipos de entrada.
O problema
O ProximityPrompt convida o jogador a interagir ao se aproximar de objetos como portas, interruptores e botões. Na lista do que ele resolve, dois itens são os que custam caro quando feitos à mão.
O primeiro: indicar com quais objetos dá para interagir. Num cenário cheio, saber o que é decoração e o que é interativo é metade da legibilidade do jogo, e essa informação precisa aparecer e sumir na hora certa.
O segundo, e é o decisivo: mostrar a entrada correta para cada tipo — teclado, controle e toque. Um botão desenhado à mão que diz "aperte E" está errado para quem está no controle e errado para quem está no celular. E a maior parte do público do Roblox está no celular.
Há ainda um problema menos visível, que aparece quando o jogo cresce: se cada objeto interativo tiver o próprio script, a lógica de interação fica espalhada por dezenas de lugares. É o que o ProximityPromptService resolve, e é o que o artefato deste guia usa.
Vale medir o tamanho do que se está evitando. Um botão de interação feito à mão precisa de um BillboardGui posicionado, de uma medida de distância que roda a cada quadro, de detecção de qual tipo de entrada o jogador está usando, de um ícone por tecla, de tratamento de toque, de uma regra para quando dois botões aparecem juntos, e de uma barra de progresso para o caso de segurar. São sete problemas, e cada um deles tem um caso de borda que só aparece num aparelho que você não tem.
O ProximityPrompt chega com os sete resolvidos e com uma vantagem que nenhuma implementação própria alcança: ele parece igual ao de outros jogos. O jogador que já viu um prompt em qualquer outro lugar da plataforma sabe o que fazer sem ler nada — e familiaridade é uma coisa que não se constrói sozinho.
Passo a passo
1. Ponha o prompt no lugar certo. Ele precisa ter como pai a parte, o modelo ou o attachment com que se vai interagir. Não é detalhe: a posição do prompt sai do pai, e um prompt solto na workspace não tem a que se referir.
No Explorer, passe o mouse sobre o objeto, clique no botão de adicionar e insira um ProximityPrompt.
2. Diga o que ele é. O prompt precisa comunicar três coisas: qual é o objeto, qual é a ação, e qual é a tecla. São três propriedades:
ObjectText— o nome opcional do objeto. "Porta da cozinha".ActionText— o nome opcional da ação. "Abrir".KeyboardKeyCode— a tecla que o jogador aperta ou segura.
Existe também o GamepadKeyCode, para a tecla do controle. Os dois textos são opcionais, mas prompt sem eles é um ícone de tecla flutuando sem contexto.
3. Controle quando ele aparece. Duas propriedades fazem o grosso.
O MaxActivationDistance define o raio em torno do prompt que ativa a visibilidade dele: quando o personagem entra nesse raio, o prompt aparece.
O RequiresLineOfSight só deixa o prompt visível quando há caminho livre da câmera até ele — e vem ligado por padrão. Isso é bom na maior parte do tempo e é a causa do bug descrito mais adiante.
4. Resolva a briga por tecla. Quando o personagem está no alcance de vários prompts, a propriedade Exclusivity decide o que aparece. No modo OnePerButton, só um prompt fica visível por tecla: prompts com teclas diferentes aparecem todos, mas com a mesma tecla só um fica visível por vez, conforme a direção da câmera.
É o comportamento que se quer numa sala com várias portas — a câmera escolhe.
5. Escolha como se aciona. O HoldDuration define quantos segundos a tecla precisa ficar apertada antes de a ação disparar; com valor zero, a ação dispara imediatamente. Segurar é o que se usa para ação destrutiva ou irreversível, porque evita o acionamento sem querer.
O ClickablePrompt define se dá para clicar no prompt. Desligado, só a tecla dispara. E existe uma exceção importante: no celular e no tablet o jogador sempre consegue clicar no prompt, independente do valor dessa propriedade.
6. Reaja no script. Dá para escutar os eventos no próprio objeto ou globalmente pelo ProximityPromptService — e a documentação recomenda o segundo: ele permite gerenciar todo o comportamento de um lugar só, sem código duplicado.
Os eventos principais: PromptTriggered, quando o jogador interage; PromptTriggerEnded, quando ele para; PromptButtonHoldBegan, quando começa a segurar num prompt com HoldDuration diferente de zero; e, do lado do cliente, PromptShown e PromptHidden, que disparam em LocalScript quando o prompt fica visível ou some.
Artefato
O script abaixo é um handler único para todos os prompts do jogo. Cada prompt declara o que ele é por um atributo, e o handler decide o que fazer — acrescentar um tipo de interação passa a ser acrescentar uma função, não espalhar um script novo dentro de mais um objeto.
E ele faz a parte que o prompt não faz: validar. O prompt é um caminho pelo qual o cliente aciona o servidor, exatamente como um RemoteEvent, e a distância que o cliente respeitou é a distância do cliente. O handler confere a distância no servidor, com uma folga deliberada — latência e movimento fazem os dois números diferirem um pouco, e sem folga o jogador honesto é recusado de vez em quando.
Erro comum
O erro mais comum é de posicionamento, e o sintoma é sempre o mesmo: o prompt não aparece, e não há erro nenhum.
A causa costuma ser o RequiresLineOfSight, que vem ligado por padrão. Ele exige caminho livre da câmera até o objeto. Um prompt colocado numa parte que está dentro de outra — a maçaneta dentro do batente, o botão embutido no painel, o prompt no centro de um modelo grande — não tem linha de visão a partir de lugar nenhum. O jogador chega perto e não vê nada.
O diagnóstico é rápido: desligue o RequiresLineOfSight. Se o prompt aparecer, o problema é geométrico, e a solução certa não é deixar desligado — é mover o prompt para um attachment na superfície visível do objeto. Desligar de vez faz o prompt atravessar paredes, que é o problema oposto.
O segundo erro é de escala do MaxActivationDistance. Valor generoso parece simpático e produz uma tela poluída, com prompts de meio cenário competindo por atenção. Vale lembrar que o raio interage com a Exclusivity: aumentar a distância aumenta quantos prompts ficam no alcance ao mesmo tempo, e é aí que a briga por tecla começa.
O terceiro é confiar no prompt como se ele fosse uma trava. Ele some da tela fora do alcance, e sumir da tela não impede nada — quem quiser acionar de longe aciona. Toda ação que mexe em estado do jogo precisa da mesma validação de servidor que um remote exigiria.
E há um quarto, específico do celular: montar a interação inteira em cima de "apertar e segurar" sem testar no toque. Com o clique sempre disponível no celular e no tablet, um HoldDuration alto vira uma interação que o jogador de telefone não entende como executar.
Próximo passo
Com os prompts funcionando, o ganho seguinte é de consistência. Escolha duas ou três teclas para o jogo inteiro e use sempre as mesmas para o mesmo tipo de ação — uma para interagir, outra para ação destrutiva com HoldDuration. O jogador aprende uma vez.
Depois, vale usar PromptShown e PromptHidden no cliente para algo além do prompt: destacar o objeto, mudar o cursor, tocar um som discreto. Eles disparam em LocalScript e são o gancho certo para o retorno visual que o prompt sozinho não dá.
E, por último, passe os olhos nos prompts existentes procurando os que disparam ação sem validação no servidor. A lista costuma ser mais curta do que parece, e cada item dela é uma porta que se abre de longe.
Fontes
- Proximity prompts | Documentation - Roblox Creator Hub — consultada em
Artefato pronto pra usar
-- ServerScriptService: UM handler para todos os ProximityPrompt do jogo.
--
-- O ProximityPromptService existe exatamente para isso: gerenciar o
-- comportamento de um lugar só, sem código duplicado. Em vez de um
-- script dentro de cada porta, o prompt declara o que ele é por um
-- atributo, e este arquivo decide o que fazer.
--
-- E ele faz a parte que o prompt NÃO faz: validar. O prompt é um
-- caminho pelo qual o cliente aciona o servidor, como um remote. A
-- distância que o cliente respeitou é a do cliente.
local ProximityPromptService = game:GetService("ProximityPromptService")
local Players = game:GetService("Players")
-- atributo "Acao" no prompt -> o que fazer
local ACOES = {}
function ACOES.abrir_porta(player, prompt)
local porta = prompt.Parent
if not porta or not porta:IsA("BasePart") then
return
end
porta.CanCollide = false
porta.Transparency = 0.7
task.delay(3, function()
if porta.Parent then
porta.CanCollide = true
porta.Transparency = 0
end
end)
end
function ACOES.acender_luz(player, prompt)
local luz = prompt.Parent and prompt.Parent:FindFirstChildOfClass("PointLight")
if luz then
luz.Enabled = not luz.Enabled
end
end
-- ── a validação que o prompt não faz ────────────────────────────────
-- O prompt some da TELA fora do alcance, mas sumir da tela não é uma
-- trava. Confira no servidor.
local function podeAcionar(player, prompt)
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return false
end
local raiz = character:FindFirstChild("HumanoidRootPart")
local alvo = prompt.Parent
if not raiz or not alvo then
return false
end
local posicao = alvo:IsA("BasePart") and alvo.Position
or (alvo:IsA("Model") and alvo:GetPivot().Position)
or (alvo:IsA("Attachment") and alvo.WorldPosition)
if not posicao then
return false
end
-- folga sobre o MaxActivationDistance: latência e movimento fazem a
-- distância do servidor diferir um pouco da do cliente. Sem folga,
-- jogador honesto é recusado de vez em quando.
local limite = prompt.MaxActivationDistance + 5
return (raiz.Position - posicao).Magnitude <= limite
end
-- ── anti-repetição ──────────────────────────────────────────────────
local ultimo = {}
local INTERVALO = 0.5
local function rapidoDemais(player)
local agora = os.clock()
if ultimo[player] and (agora - ultimo[player]) < INTERVALO then
return true
end
ultimo[player] = agora
return false
end
ProximityPromptService.PromptTriggered:Connect(function(prompt, player)
local nome = prompt:GetAttribute("Acao")
local acao = nome and ACOES[nome]
if not acao then
warn("prompt sem acao valida: " .. prompt:GetFullName())
return
end
if rapidoDemais(player) or not podeAcionar(player, prompt) then
return
end
local ok, erro = pcall(acao, player, prompt)
if not ok then
warn("acao " .. nome .. " falhou: " .. tostring(erro))
end
end)
Players.PlayerRemoving:Connect(function(player)
ultimo[player] = nil
end)


