BindableEvent: avisar vários scripts sem que nenhum conheça o outro
BindableEvent liga comportamento entre scripts do mesmo lado da fronteira. O uso clássico é jogo por rodada — e a documentação avisa: quando o que você quer é compartilhar dado, módulo costuma ser melhor.
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

Todo jogo com rodada chega no mesmo ponto: quando a partida começa, cinco coisas precisam acontecer em cinco scripts diferentes. Cronômetro, placar, spawn, música, interface.
A solução ingênua é um script que conhece os outros quatro e chama cada um. Funciona, e cria um script que precisa ser editado toda vez que alguém acrescenta um sistema novo.
O BindableEvent resolve isso invertendo quem conhece quem.
O problema
A definição: BindableEvent e BindableFunction ligam comportamento entre scripts do mesmo lado da fronteira cliente-servidor. Essa última parte importa — eles não atravessam a fronteira. Para isso existem os remotes.
E a documentação nomeia o caso de uso principal: o mais comum é jogo com estrutura de rodada. O exemplo dado é exatamente o do começo deste texto — um evento de partida iniciada que faz outros scripts começarem o cronômetro e mostrarem o placar, com um evento correspondente de partida encerrada que avisa quando levar os jogadores de volta ao saguão.
Eles costumam ser usados no servidor, mas funcionam no cliente também.
E aqui vem uma ressalva que a própria documentação faz, e que economiza muito código desnecessário: eventos ajudam a deixar o código mais modular, mas para compartilhar dado entre scripts o módulo costuma ser a alternativa melhor.
É uma distinção que vale interiorizar. BindableEvent é para avisar que algo aconteceu. Módulo é para ler um valor. Quem usa evento para transportar estado acaba com um jogo onde a verdade está em trânsito, e ninguém sabe qual é o valor atual sem esperar o próximo disparo.
Passo a passo
1. Escolha onde o objeto mora. A recomendação é ServerScriptService para comunicação entre scripts de servidor, e ReplicatedStorage entre scripts de cliente.
2. Crie o objeto e dê um nome que descreva o fato, não a reação: PartidaComecou, não IniciarCronometro. Quem dispara não deve saber o que vai acontecer depois.
3. Conecte e dispare. O uso é conectar uma função ao evento num script e disparar de outro.
Duas propriedades do Fire() definem o comportamento. A primeira: ao disparar, o script que dispara não fica esperando. Ele segue no mesmo quadro, e os ouvintes rodam por conta própria.
A segunda: como todo evento, ele cria threads de cada função conectada — então, se uma dá erro, as outras continuam. É resiliência de graça: um sistema quebrado não derruba os outros quatro.
4. Use BindableFunction quando precisar de resposta. Ele permite comunicação síncrona e de mão dupla: define um callback próprio, chamado manualmente pelo método Invoke().
A diferença operacional é grande. O código que invoca fica esperando até o callback correspondente ser encontrado. E há uma consequência severa: se o callback nunca tiver sido definido, o script que invocou não retoma a execução.
Não é erro, não é nil, não é timeout. É uma thread parada para sempre.
Um detalhe sobre Fire que muda como se escreve o resto. Como o disparo não espera, o script que avisa continua imediatamente — e isso significa que ele não pode assumir que os ouvintes terminaram. Código escrito como "aviso que a rodada começou e já conto os jogadores vivos" lê um estado que os ouvintes ainda vão montar.
Quando a sequência importa de verdade, a ferramenta é outra: BindableFunction, que espera. Usar evento e depois torcer para os ouvintes terem sido rápidos é uma corrida que se perde em servidor cheio, e que ganha em todo teste.
Artefato
O módulo abaixo aplica o padrão que a própria documentação sugere: BindableEvent dentro de um módulo, com sintaxe mais limpa.
O ganho real não é de sintaxe. É que os eventos do jogo passam a estar declarados num lugar só — e um nome errado dá error na hora, em vez de criar uma conexão perfeitamente válida a um objeto que não existe, que nunca vai disparar e que não vai reclamar disso.
A segunda metade dele resolve o problema de ordem, e é a próxima seção.
Vale registrar uma diferença de vocabulário que confunde quem vem de outras linguagens: aqui, "evento" não é uma mensagem numa fila. É uma lista de funções que o motor chama quando alguém dispara. Não há entrega garantida, não há histórico, não há quem chegou primeiro — quem conectou depois do disparo simplesmente não recebe aquele.
Isso tem uma consequência prática na inicialização: um script que dispara "jogo pronto" antes de outro conectar deixa o segundo esperando para sempre um aviso que já passou. Para fato que já aconteceu, evento é a ferramenta errada — módulo com um valor booleano resolve, e quem chega tarde ainda consegue ler.
Antes dos erros, vale fixar a divisão em uma frase, porque ela decide a ferramenta em quase todo caso: um lado avisa e não espera; o outro pergunta e espera. BindableEvent é o primeiro, BindableFunction é o segundo, e trocá-los produz ou um jogo que segue antes da hora ou uma thread parada para sempre.
Erro comum
Depender da ordem das conexões. A documentação é explícita: dá para conectar várias funções ao mesmo BindableEvent, mas o Luau as executa em ordem imprevisível.
É o erro mais traiçoeiro do assunto porque ele funciona em teste. Com duas ou três conexões, a ordem costuma ser estável o bastante para o jogo parecer certo. Ela quebra quando o jogo cresce, quando um script demora mais para carregar, ou num servidor sob carga — ou seja, em produção, de vez em quando.
A recomendação oficial: para garantir ordem, junte as funções numa só e conecte essa.
O artefato faz isso preservando a legibilidade dos passos separados — e com um cuidado: dentro de uma função única, um erro pararia os passos seguintes, o oposto do comportamento de eventos independentes. O pcall em cada passo devolve a resiliência sem devolver a desordem.
Invocar callback que não existe. O caso do BindableFunction descrito acima. O script fica parado, silenciosamente, e tudo o que dependia daquela linha nunca acontece. O sintoma é um sistema que "não faz nada" sem nenhum erro no output.
É especialmente comum na inicialização: o script que invoca roda antes do script que define o callback. A ordem de execução de scripts não é garantida, e essa corrida perdida trava para sempre.
Usar evento onde cabia módulo. Já mencionado, e vale o exemplo: um BindableFunction que devolve "quantos jogadores estão vivos" é um módulo com passos extras. A função existe, o dado existe, e você acrescentou uma indireção que só pode dar errado.
A regra: se a resposta é um valor que já está em algum lugar, é módulo. Se é um trabalho que precisa ser feito agora por outro sistema, é BindableFunction.
Nomear pelo efeito. Um evento chamado MostrarPlacar amarra quem dispara ao que acontece. Quando o segundo sistema quiser reagir, o nome fica errado — e o nome errado é o começo de um código onde ninguém confia nos nomes.
Vale ainda um cuidado com conexões que sobrevivem ao que as criou. Conectar dentro de uma função que roda a cada rodada acumula uma conexão por rodada, e na décima o mesmo aviso dispara dez reações. O sintoma é um jogo que fica progressivamente mais estranho ao longo da sessão, e que volta ao normal quando o servidor reinicia — o que atrasa o diagnóstico, porque parece instabilidade.
A regra: conecte uma vez, na inicialização, ou guarde a conexão e desconecte junto com o que a criou.
Criar o objeto por script sem garantir que ele exista antes. Quem cria o BindableEvent em tempo de execução e conecta noutro script precisa que a criação tenha acontecido primeiro. O WaitForChild resolve; o FindFirstChild devolve nil na corrida perdida, e a conexão simplesmente não é feita — sem erro, sem aviso.
Próximo passo
O exercício útil é uma auditoria de acoplamento: abra o script que comanda a rodada do seu jogo e conte quantos outros scripts ou módulos ele menciona pelo nome.
Cada menção é uma dependência que precisa ser editada quando aquele sistema mudar. Um evento no lugar de cinco chamadas transforma esse script no que ele deveria ser — um que sabe quando a rodada começa e nada sobre o que isso desencadeia.
Depois disso, vale escrever a lista dos fatos que o seu jogo tem. Partida começou, partida terminou, jogador eliminado, objetivo capturado. É uma lista curta, e ela costuma revelar que dois sistemas estavam detectando o mesmo fato por conta própria, cada um do seu jeito.
E guarde a divisão que organiza tudo: evento avisa, módulo guarda, remote atravessa. Cada ferramenta tem um trabalho, e quase todo emaranhado de código vem de usar uma delas para o trabalho de outra.
Fontes
Artefato pronto pra usar
-- ServerScriptService > ModuleScript "Rodada"
--
-- O padrão que a documentação sugere: BindableEvent DENTRO de um módulo.
-- Os scripts não procuram objeto no Explorer nem precisam saber onde o
-- evento mora -- eles chamam uma função com nome.
--
-- O ganho não é de sintaxe. É que o conjunto de eventos do jogo passa a
-- estar declarado num lugar só, e um nome errado dá erro NA HORA em vez
-- de criar uma conexão silenciosa que nunca dispara.
local Rodada = {}
-- Os eventos do jogo, declarados. Acrescentar um é acrescentar linha.
local NOMES = { "comecou", "terminou", "jogadorEliminado" }
local eventos = {}
for _, nome in NOMES do
local b = Instance.new("BindableEvent")
b.Name = nome
b.Parent = script
eventos[nome] = b
end
local function pegar(nome)
local e = eventos[nome]
if not e then
-- erro NA HORA, e não uma conexão que nunca dispara
error("evento de rodada inexistente: " .. tostring(nome), 3)
end
return e
end
function Rodada.quando(nome, callback)
return pegar(nome).Event:Connect(callback)
end
function Rodada.avisar(nome, ...)
-- Fire NÃO espera: quem avisa segue o próprio caminho no mesmo quadro
pegar(nome):Fire(...)
end
--[[
═══════════════════════════════════════════════════════════════════════
A ORDEM É IMPREVISÍVEL -- e é por isso que existe esta parte
═══════════════════════════════════════════════════════════════════════
Conectar cinco funções a "comecou" NÃO garante que elas rodem na ordem
em que foram conectadas. Para o que PRECISA de ordem, a documentação
manda juntar tudo numa função só.
Esta função monta essa "função só" a partir de uma lista, preservando a
legibilidade de ter passos separados.
]]
function Rodada.emOrdem(nome, passos)
return Rodada.quando(nome, function(...)
for i, passo in passos do
local ok, erro = pcall(passo, ...)
if not ok then
-- Numa função única, um erro PARARIA os passos seguintes --
-- o oposto do comportamento de eventos separados. O pcall
-- devolve a resiliência sem devolver a desordem.
warn("passo " .. i .. " de " .. nome .. " falhou: " .. tostring(erro))
end
end
end)
end
return Rodada
--[[
USO:
-- Script A: quem comanda a rodada
local Rodada = require(script.Parent.Rodada)
Rodada.avisar("comecou", { duracao = 120, mapa = "Vila" })
-- Script B: cronômetro e placar, SEM ordem entre si
Rodada.quando("comecou", function(info) iniciarCronometro(info.duracao) end)
Rodada.quando("comecou", function(info) mostrarPlacar() end)
-- Script C: limpeza que PRECISA de ordem
Rodada.emOrdem("terminou", {
function() congelarJogadores() end,
function() calcularVencedores() end,
function() voltarParaLobby() end,
})
]]


