Streaming de instâncias: a peça não foi destruída, ela foi para nil
O streaming vem ligado por padrão em place novo e não dá para mexer nele por script. A consequência que quebra código: quando uma instância sai, ela é reparentada para nil — e os sinais de remoção disparam sem que nada tenha sido destruído.
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

Existe um erro de streaming que não aparece em teste nenhum do Studio e aparece em todo jogador: o código trata uma peça que saiu do alcance como uma peça que foi destruída.
Os dois casos disparam o mesmo sinal. E o primeiro é reversível, enquanto o segundo não é. Este guia percorre o streaming a partir dessa distinção, porque é dela que sai quase todo bug do assunto.
O problema
O streaming de instâncias permite ao motor carregar e descarregar conteúdo 3D da Workspace dinamicamente. Os ganhos que a documentação lista são três, e são reais:
- Entrada mais rápida: o jogador começa numa parte do mundo enquanto o resto carrega ao fundo.
- Memória: o jogo roda em aparelho com menos memória, porque o conteúdo entra e sai conforme a necessidade.
- Desempenho: o servidor gasta menos tempo e banda sincronizando o mundo com os jogadores.
Há ainda o nível de detalhe, que mantém modelos e terreno distantes visíveis mesmo sem estarem carregados — e o SLIM entrega a maior fidelidade disso para modelos e avatares.
Duas frases sobre o controle mudam a discussão inteira. A primeira: o streaming é controlado pela propriedade StreamingEnabled da Workspace, e ela vem ligada por padrão em place novo criado no Studio. A segunda: essa propriedade não pode ser definida por script.
Juntas, significam que a maioria dos jogos criados recentemente tem streaming ligado sem que ninguém tenha decidido isso — e que não existe um jeito de desligar temporariamente no código para contornar um bug. Ou o jogo funciona com streaming, ou a configuração muda no editor.
Passo a passo
1. Entenda o escopo. O streaming vale exclusivamente para instâncias descendentes da Workspace. Instâncias em outros contêineres — ReplicatedStorage, ReplicatedFirst — não são elegíveis.
A documentação faz questão de dizer o corolário, porque ele engana: pôr um modelo atômico na ReplicatedStorage não garante replicação atômica. As propriedades de streaming do modelo não significam nada fora da Workspace.
2. Saiba o que não vem na entrada. Quando o jogador entra, as instâncias da Workspace são replicadas exceto: as BaseParts; os modelos marcados como Atomic, Persistent ou PersistentPerPlayer; os modelos Nonatomic, que é o padrão, quando o ModelStreamingBehavior está em Improved; e os descendentes de tudo isso.
É uma lista curta que cobre quase todo o cenário de um jogo. Na prática: o mundo não está lá quando o seu script roda.
3. Saiba como a saída funciona. Durante a partida, o cliente pode remover regiões de BaseParts, conforme o StreamOutBehavior. O processo começa pelas regiões mais distantes dos focos de replicação e vai se aproximando conforme necessário.
Há um piso: regiões dentro do StreamingMinRadius nunca saem. É a garantia mínima em volta do jogador, e é onde vale pôr o que não pode faltar.
Duas isenções importantes. Modelo sem nenhuma BasePart descendente — como um usado só de contêiner para scripts — é isento de sair, a não ser que vire descendente de uma BasePart. E instância criada ou clonada por script de cliente é isenta, a não ser que esteja sob uma instância criada pelo servidor.
Essa segunda isenção é uma ferramenta: efeito visual que o cliente cria fica onde está, desde que não seja pendurado numa peça do servidor.
4. Conheça as isenções como ferramenta. As duas isenções da saída não são curiosidade — elas são o mecanismo pelo qual se garante que algo permaneça.
Um modelo puramente organizacional, sem nenhuma BasePart, fica onde está. Isso faz dele o lugar certo para scripts e valores que precisam existir o tempo todo, independentemente de onde o jogador esteja. Pendurar esse mesmo modelo dentro de uma peça do cenário o transforma em candidato a sair junto com ela.
E o que o cliente cria fica, contanto que não seja pendurado numa instância do servidor. Um efeito de partícula criado localmente e colocado na Workspace persiste; o mesmo efeito colocado dentro da peça que o originou some quando a peça sai.
Artefato
O módulo abaixo acompanha uma instância que pode entrar e sair, e existe para separar as duas coisas que o mesmo sinal não separa.
O quandoEntra roda toda vez que a peça está presente — na primeira vez e em cada retorno. O quandoSai roda quando ela some, e pode rodar várias vezes na mesma sessão.
Repare no que o exemplo de uso não faz no quandoSai: não apaga progresso, não conta como item perdido, não se desconecta. A peça pode voltar em segundos.
E repare no que ele faz no quandoEntra: reaplica a cor. Isso não é redundância — é a próxima seção.
Uma nota sobre como testar isto, porque é a parte que não acontece sozinha: no Studio, com o mapa inteiro carregado e um jogador só, o streaming quase nunca exerce o caminho de saída. O teste que vale é andar até o outro extremo do mapa e voltar, observando o que acontece com o objeto que o seu código acompanha. Se o seu mapa não for grande o bastante para isso, o problema não desapareceu — ele só vai aparecer quando o mapa crescer.
Erro comum
O erro central está numa frase que vale ler devagar: ao sair, a instância é reparentada para nil, para que o estado Luau existente se reconecte se ela voltar.
Daí saem duas consequências que quebram código.
A primeira: sinais de remoção como ChildRemoved e DescendantRemoving disparam no pai ou no ancestral — mas a instância não é destruída no mesmo sentido de uma chamada de Destroy().
O código que escuta ChildRemoved para concluir "o item foi coletado", "o inimigo morreu", "a plataforma foi removida" vai concluir isso toda vez que o jogador se afastar o bastante. E vai concluir de novo quando ele voltar e se afastar outra vez. Num servidor cheio, o mesmo evento dispara dezenas de vezes por sessão.
O teste que distingue é o Parent: saída de streaming deixa a instância viva com Parent nulo, enquanto o que foi destruído não volta. Na prática, o caminho robusto é o do artefato — tratar entrada e saída como estados que alternam, em vez de eventos terminais.
A segunda consequência é mais silenciosa: mudança de propriedade que existe só no cliente e não foi replicada pode ser perdida se a instância sair e voltar.
Esse é o bug que ninguém encontra. O cliente pinta uma peça de verde para marcar que já foi usada, ou esconde um objeto que o jogador coletou localmente. O jogador se afasta, volta, e a peça está do jeito original. Não há erro, não há log, e o comportamento depende da distância — que é a variável que ninguém pensa em testar.
A correção é a do artefato: reaplicar o estado local a cada entrada, em vez de aplicar uma vez e confiar.
Há ainda um detalhe de ordem que economiza depuração: como o StreamingEnabled não se define por script, não existe um modo "streaming desligado só neste teste". A configuração é do place, e mudá-la para investigar um bug muda o comportamento de tudo. O caminho mais rápido costuma ser o inverso — assumir que o streaming está ligado e escrever o código de cliente já preparado para a peça não estar lá.
Próximo passo
O assunto seguinte são as assembleias físicas, porque elas seguem regras próprias e são onde o streaming encosta no desempenho.
Assembleias entram como unidades completas, junto com as Constraints e os Attachments associados — o que garante física consistente no cliente. A única exceção é a assembleia ancorada, em que só as BaseParts dentro do raio entram. E elas não saem até que todas as suas BaseParts estejam elegíveis a sair.
Daí vem uma recomendação que é de arquitetura, não de código: evite assembleia móvel com número desnecessariamente grande de instâncias, porque todas entrando em uníssono pode causar picos de rede e CPU.
Isso muda como se constrói veículo, plataforma móvel e máquina grande. Um trem de duzentas peças articuladas é uma assembleia que entra de uma vez, para todo jogador que chegar perto — e o pico acontece no pior momento possível, que é quando alguém está se aproximando em velocidade.
O exercício prático: liste as assembleias móveis do seu jogo e conte as peças de cada uma. As maiores são candidatas a serem simplificadas, divididas ou ancoradas quando paradas.
E, antes de tudo isso, vale a conferência de base: abra o seu place e veja se o StreamingEnabled está ligado. Se estiver — e em place novo ele está —, todo o código de cliente que assume que a Workspace está inteira já é código com bug esperando distância suficiente para aparecer.
Fontes
- Instance streaming | Documentation - Roblox Creator Hub — consultada em
Artefato pronto pra usar
-- LocalScript: acompanhar uma instância que o streaming pode tirar e
-- devolver, SEM tratar a saída como destruição.
--
-- A distinção que este arquivo existe para fazer:
--
-- SAIU DO STREAMING -> Parent virou nil, o objeto CONTINUA existindo,
-- e ele pode voltar. Não limpe o estado.
-- FOI DESTRUÍDO -> não volta nunca. Aí sim, limpe.
--
-- Os dois disparam ChildRemoved no pai. Olhar só o sinal não distingue.
local Observador = {}
Observador.__index = Observador
-- `quandoEntra` roda toda vez que a peça está presente e utilizável.
-- `quandoSai` roda quando ela some do mundo do cliente -- e pode rodar
-- várias vezes ao longo da sessão, alternando com `quandoEntra`.
function Observador.novo(pai, nome, quandoEntra, quandoSai)
local self = setmetatable({
pai = pai,
nome = nome,
quandoEntra = quandoEntra,
quandoSai = quandoSai,
atual = nil,
conexoes = {},
}, Observador)
-- ChildAdded pega tanto a criação quanto o RETORNO do streaming
table.insert(self.conexoes, pai.ChildAdded:Connect(function(filho)
if filho.Name == nome then
self:_entrou(filho)
end
end))
table.insert(self.conexoes, pai.ChildRemoved:Connect(function(filho)
if filho == self.atual then
self:_saiu()
end
end))
local ja = pai:FindFirstChild(nome)
if ja then
self:_entrou(ja)
end
return self
end
function Observador:_entrou(instancia)
if self.atual == instancia then
return
end
self.atual = instancia
if self.quandoEntra then
task.spawn(self.quandoEntra, instancia)
end
end
function Observador:_saiu()
local anterior = self.atual
self.atual = nil
if self.quandoSai then
task.spawn(self.quandoSai, anterior)
end
end
function Observador:destruir()
for _, c in self.conexoes do
c:Disconnect()
end
self.conexoes = {}
self.atual = nil
end
--[[
USO -- repare no que NÃO está aqui:
local alavanca = Observador.novo(workspace, "Alavanca",
function(peca)
-- REAPLICAR o visual local a cada entrada. Mudança que só
-- existe no cliente se perde quando a peça sai e volta, então
-- ela precisa ser refeita aqui, não feita uma vez lá atrás.
peca.Color = Color3.fromRGB(0, 255, 0)
end,
function()
-- NÃO apague progresso, NÃO conte como "jogador perdeu o item",
-- NÃO desconecte o observador. A peça pode voltar em segundos.
end)
]]
return Observador


