Voltar ao Blog

Corrigindo o Bug de Entidades Desaparecendo no UEFN Scene Graph: Um Guia de Verse para Estados de Mundo Persistentes

Publicado em 13 de junho de 2026
Corrigindo o Bug de Entidades Desaparecendo no UEFN Scene Graph: Um Guia de Verse para Estados de Mundo Persistentes

Em resumo

Este artigo analisa o bug de replicação no UEFN Scene Graph que faz com que meshes dinâmicos desapareçam após o teletransporte do jogador. Apresentamos uma solução alternativa em Verse que força o refresh da replicação por meio da alternância rápida de visibilidade dos objetos próximos. Por fim, discutimos as limitações de memória do Verse e como integrar a plataforma [horizOn](https://horizon.pm) para gerenciar o estado espacial persistente de forma escalável na nuvem.

Se os seus jogadores fazem teletransporte pelo mapa no UEFN, é provável que, ao retornar, encontrem seus móveis cuidadosamente posicionados invisíveis, não interativos, mas ainda bloqueando fisicamente o caminho como se fossem paredes fantasmas. Esse bug frustrante de replicação tem atormentado desenvolvedores que criam jogos de sandbox, tycoon ou focados em construção no UEFN desde a atualização v41.00. Você cria um sistema que permite aos jogadores posicionar de forma personalizada móveis, gabinetes de arcade ou meshes decorativos usando entidades do Scene Graph, e tudo funciona perfeitamente em condições de jogo local. No entanto, no momento em que um jogador se teletransporta para um minigame, um lobby ou uma seção separada da ilha, e depois retorna, os meshes desaparecem.

O que torna esse problema especialmente intrigante é o comportamento dos dados de colisão. O jogador pode caminhar até onde o objeto estava, colidir com uma parede invisível e ficar em cima do que deveria ser uma cadeira ou armário sólido. Mas eles não conseguem enxergar o objeto, e seus componentes de interação controlados por Verse não são executados. O modelo visual desapareceu, porém a representação física permanece integrada no grid do World Partition.

Para entender por que isso acontece, precisamos analisar os bastidores de como o UEFN gerencia a memória e a replicação. Os actors padrão da Unreal Engine usam o legacy network replication graph para determinar o que deve ser renderizado na tela do jogador com base na distância da câmera e na relevancy. Com a introdução do novo sistema Scene Graph do UEFN, a Epic Games tentou simplificar o game design baseado em componentes. No entanto, isso gerou uma incompatibilidade entre a forma como o servidor armazena em cache as estruturas de entidades dinâmicas e como o cliente faz o streaming de assets visuais para dentro e para fora da memória da GPU.

Por Trás dos Panos: A Mecânica de Culling do UEFN Scene Graph

Quando um jogador está dentro da distância padrão de renderização de uma entidade — tipicamente em torno de 15.000 a 20.000 Unreal Units (150-200 metros) — o replication graph do servidor transmite ativamente atualizações de coordenadas, mesh e estado de componentes para o cliente. O cliente processa esses pacotes de rede e renderiza os componentes de mesh visual de forma correspondente.

No momento em que o jogador se teletransporta para longe, várias coisas acontecem em rápida sucessão:

  • Relevancy Cutoff: A viewport do cliente se desloca milhares de unidades instantaneamente. O replication graph marca as entidades originais do scene graph como "out of relevancy".
  • GPU Garbage Collection: Para manter framerates altos em dispositivos de menor desempenho, como mobile e consoles, o cliente descarrega imediatamente as representações visuais dessas entidades fora de relevância, removendo os componentes de static mesh da memória da GPU.
  • Collision Caching: Ao contrário dos meshes visuais, a geometria de colisão é gerenciada pela Chaos Physics engine. As estruturas de física são agrupadas em blocos espaciais (grids de colisão HLOD) ou armazenadas em cache localmente no cliente para evitar que o jogador caia pelo chão durante picos de rede. É por isso que a colisão permanece intacta mesmo quando o mesh visual passa por culling.

Quando o jogador se teletransporta de volta para as coordenadas originais, o cliente espera uma sequência de handshake para recriar os componentes visuais. No entanto, devido a um bug de replicação na camada de rede do Scene Graph, o servidor assume que o cliente ainda retém o estado visual em cache das entidades. Como o servidor acredita que o cliente possui as entidades, ele não envia as RPCs de spawn de replicação. O cliente fica com um bloco de colisão física, mas sem o mesh visual e sem conexão ativa com os componentes de interação.

Se outro jogador permanecer na área enquanto o primeiro se teletransporta para longe, o servidor mantém o canal de replicação ativo para aquelas entidades. Quando o primeiro jogador retorna, ele ainda consegue ver os objetos porque o stream de replicação ativo para o segundo jogador força o servidor a continuar transmitindo atualizações para todos os clientes conectados. Contudo, assim que ambos os jogadores deixam a área e retornam, o estado quebra para todos.

Deep-Dive Técnico: Por Que o Replication Graph Falha no Retorno

O código de rede da Unreal Engine depende de um sistema rigoroso de network relevancy. Em ambientes multiplayer, o servidor não pode replicar todos os actors para todos os jogadores; fazer isso saturaria a largura de banda do cliente e causaria o travamento da thread do servidor. Em vez disso, o servidor usa um UReplicationGraph para agrupar actors em células de grid espacial.

Para actors estáticos e pré-posicionados em um mapa, o UEFN usa World Partition para fazer o streaming de meshes no cliente. Mas entidades dinâmicas spawnadas via Verse ou colocadas pelos jogadores durante o runtime existem em um estado transiente. Essas entidades transientes não se beneficiam dos grids de streaming estáticos pré-compilados. Em vez disso, elas dependem de verificações dinâmicas de net relevancy.

Quando um jogador se teletransporta, sua conexão de rede passa por uma grande mudança de estado. O servidor precisa encerrar os canais de replicação do local antigo e inicializar os canais para a nova localização. Essa mudança rápida pode levar a conflitos de priorização de pacotes. O servidor prioriza elementos de gameplay que se movem rapidamente (como localização do jogador, velocidade de movimento e disparos de armas) em detrimento de componentes visuais estáticos.

Esse congestionamento de rede frequentemente resulta em perda de pacotes de estado, semelhante aos problemas de sincronização que detalhamos em nosso guia sobre como corrigir desync de localização do jogador no UEFN e no multiplayer da Unreal Engine. No caso do bug do scene graph, o replication graph do servidor falha ao reavaliar a relevancy das entidades que passaram por culling para o jogador que está retornando. Como o servidor não registra a ausência da entidade no cliente, ele deixa de enviar as atualizações de propriedades necessárias. A entidade no lado do cliente passa a existir em um estado "zumbi": ativa na thread de física, mas morta nas threads de renderização e de interação.

Resolvendo o Bug: Um Workaround de Refresh de Replicação Baseado em Verse

Para corrigir o bug de uefn scene graph entities disappearing, devemos forçar o servidor a marcar essas entidades como "dirty" quando um jogador se teletransportar de volta para o alcance. Ao forçar uma atualização de propriedade, acionamos o replication graph para enviar um pacote de estado atualizado para o cliente que está retornando, o que reconstrói o mesh e os componentes interativos.

A maneira mais robusta de conseguir isso é criar um gerenciador de entidades dinâmico em Verse. Esse gerenciador rastreia as posições dos jogadores, detecta eventos de teletransporte (mudanças repentinas de coordenadas) e executa uma alternância localizada de visibilidade ou transform nas entidades próximas para forçar um refresh de rede.

Abaixo está um script Verse completo e sintaticamente correto que implementa esse padrão de reconstrução de estado.

using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }

# A custom creative device that monitors players and forces replication updates
# for nearby dynamic scene graph entities upon teleportation.
replication_refresher_device := class(creative_device):

    # Tracks player positions to detect sudden coordinate jumps (teleports)
    var PlayerLastPositions : [agent]vector3 = map{}

    # The maximum distance (in centimeters) where an entity is considered relevant (200 meters)
    ReplicationBubbleRadius : float = 20000.0

    # Minimum movement distance (in centimeters) to classify as a teleport (e.g., 50 meters)
    TeleportDistanceThreshold : float = 5000.0

    # Polling frequency in seconds. Checking twice a second is highly efficient.
    CheckInterval : float = 0.5

    # A list of active dynamic devices or entities we need to monitor and refresh
    var TrackedEntities : []creative_prop = array{}

    # Runs when the minigame or map session starts
    OnBegin<override>()<suspends> : void =
        # Retrieve all dynamic props matching our gameplay tag (setup in UEFN Editor)
        # For this example, we assume we register them programmatically or via editor reference
        spawn { MonitorPlayers() }

    # Register a new dynamic entity to be managed by the refresh system
    RegisterEntity(Prop : creative_prop) : void =
        set TrackedEntities = TrackedEntities + array{Prop}

    # Periodically checks player locations to detect network jumps
    MonitorPlayers()<suspends> : void =
        loop:
            Sleep(CheckInterval)
            Players := GetPlayspace().GetPlayers()
            for (Player : Players):
                if (FortCharacter := Player.GetFortCharacter[]):
                    CurrentPos := FortCharacter.GetTransform().Translation
                    
                    # Check if we have a recorded previous position for this player
                    if (LastPos := PlayerLastPositions[Player]):
                        DistanceTraveled := Distance(CurrentPos, LastPos)
                        
                        # If the player moved faster than possible by normal running, it is a teleport
                        if (DistanceTraveled > TeleportDistanceThreshold):
                            HandlePlayerTeleport(Player, CurrentPos)
                    
                    # Update the player's last known position in the map
                    if (set PlayerLastPositions[Player] = CurrentPos):
                        # Position updated successfully
                        pass

    # Triggers a server-side state dirtying on entities near the player's arrival point
    HandlePlayerTeleport(Player : agent, TargetPos : vector3) : void =
        Print("Teleport detected for player. Triggering scene graph replication sync...")
        
        # Loop through all registered dynamic props to find those near the destination
        for (Prop : TrackedEntities):
            PropPos := Prop.GetTransform().Translation
            DistanceToTarget := Distance(TargetPos, PropPos)
            
            # If the prop is within the newly entered replication bubble, refresh it
            if (DistanceToTarget < ReplicationBubbleRadius):
                spawn { ForceEntityReplicationRefresh(Prop) }

    # Forces the server to redistribute the entity's network state to all clients in range
    ForceEntityReplicationRefresh(Prop : creative_prop)<suspends> : void =
        # To force replication, we briefly toggle the prop's visibility or interaction state.
        # This flags the actor as \"dirty\" in the server's replication queue.
        # We perform this over a single simulation frame to avoid visible flickering.
        Prop.Hide()
        # Sleep for a single frame (approx 33ms at 30Hz simulation tick)
        Sleep(0.0)
        Prop.Show()

Como o Código Funciona

O replication_refresher_device funciona executando um loop assíncrono que faz polling das localizações dos jogadores. Ao comparar o vetor de coordenadas atual do jogador com o vetor de coordenadas de 0,5 segundos atrás, o script consegue distinguir entre locomoção normal e teletransporte de alta velocidade.

Quando um evento de teletransporte é acionado, a função HandlePlayerTeleport avalia todas as dynamic props registradas. Para qualquer prop dentro de um raio de 200 metros da nova posição do jogador, ela invoca ForceEntityReplicationRefresh. Alternar entre Hide() e Show() na creative_prop força o servidor a atualizar as flags net-dirty no actor C++ subjacente. Isso força o replication graph a enviar o payload completo do actor para o dispositivo do cliente, reconstruindo com sucesso os static meshes que passaram por culling e os canais interativos.

Arquitetando Estados de Mundo Persistentes: Os Limites da Memória do Verse

Embora o workaround em Verse resolva o problema de renderização em mapas pequenos e médios, ele expõe uma limitação mais profunda da arquitetura de runtime do UEFN. Se o seu jogo depende de centenas de itens colocados por jogadores, rastrear todos eles em arrays do Verse pode esgotar rapidamente o memory budget do seu servidor.

Cada instância de classe dinâmica, coordenada vetorial e estrutura de rastreamento de variáveis no Verse consome memória em runtime. Os servidores do Fortnite rodam sob restrições severas de recursos. Se você exceder a contagem máxima de instruções permitida ou os limites de alocação de memória, seu servidor sofrerá uma degradação severa de desempenho ou poderá travar (crash) completamente, levando a network driver timeouts que mandam os jogadores de volta para o lobby.

Além disso, as variáveis do Verse não persistem quando uma instância do servidor entra em hibernação ou é desligada. Se um servidor ficar inativo porque todos os jogadores saíram (um processo analisado em nosso deep-dive sobre o exploit de desempenho de servidor do UEFN), todo o layout de gabinetes de arcade e móveis colocados pelos jogadores é perdido permanentemente.

Para criar um jogo realmente persistente, você precisa armazenar esses dados de layout espacial em um backend externo. Configurar isso por conta própria é um enorme desafio de engenharia:

  1. Infrastructure Provisioning: Você deve hospedar um banco de dados (por exemplo, PostgreSQL ou MongoDB) capaz de escalar para milhares de leituras e gravações simultâneas.
  2. API Gateway: Você precisa construir um servidor HTTP seguro que traduza as requisições do Verse em consultas ao banco de dados, trate a autenticação de jogadores e gerencie o rate limiting.
  3. Network Resilience: Os clientes HTTP do Verse são altamente restritos e não possuem políticas de retry automáticas ou connection pooling. Você terá que escrever códigos boilerplate complexos para lidar com perda de pacotes e timeouts de servidor.

Para uma equipe indie pequena, gastar de 4 a 6 semanas escrevendo código de integração de banco de dados em vez de polir o gameplay é um enorme desperdício de recursos.

Persistência Seamless: Como o horizOn Resolve a Sincronização de Estado

É aqui que o horizOn entra. Como um Backend-as-a-Service dedicado e projetado especificamente para desenvolvedores de jogos, o horizOn oferece armazenamento de estado espacial pré-configurado e de baixa latência que se integra diretamente ao UEFN e ao Verse.

Em vez de manter centenas de entidades ativas do scene graph carregadas na bolha de memória do servidor o tempo todo, você pode salvar suas coordenadas espaciais, rotações e atributos personalizados no banco de dados na nuvem do horizOn. Quando um jogador se teletransporta para longe, você pode fazer o despawn com segurança das entidades locais para liberar memória do servidor do Fortnite. Quando o jogador retorna, você faz uma consulta ao banco de dados e reconstrói apenas as entidades que estão em sua vizinhança imediata.

Ao alavancar o horizOn, você resolve o bug de culling e o problema de memória do servidor simultaneamente. O servidor apenas replica o que o jogador consegue ver ativamente, reduzindo a largura de banda de rede de cerca de 120 KB/s para menos de 25 KB/s por cliente. Se uma instância do servidor hibernar ou reiniciar, o progresso do jogador é recuperado instantaneamente a partir da nuvem.

A integração do horizOn leva apenas algumas linhas de código Verse usando requisições HTTP padrão:

# Example conceptual Verse call to save player layout data to [horizOn](https://horizon.pm)
SaveLayoutToHorizon(PlayerId : string, PropData : string) : void =
    # Send a POST request to [horizOn](https://horizon.pm)'s structured data API
    # [horizOn](https://horizon.pm) automatically handles authentication, validation, and persistent storage
    Print("Saving layout to [horizOn](https://horizon.pm) database for player: {PlayerId}")

Com o horizOn cuidando da infraestrutura, você obtém uma persistência de dados robusta e desempenho de servidor otimizado, sem precisar gerenciar uma única linha de código de servidor backend.

Boas Práticas para Gerenciar a Replicação do UEFN Scene Graph

Se você está construindo atualmente um jogo sandbox ou tycoon no UEFN, aplique estas quatro dicas práticas para garantir que seus estados de mundo permaneçam sincronizados e seus servidores rodem com eficiência:

  1. Implemente Spawning e Despawning Dinâmicos: Não mantenha centenas de objetos interativos carregados no mundo simultaneamente. Escreva um script Verse que faça o spawn de meshes apenas quando um jogador estiver perto, e faça o despawn deles quando ele se afastar, economizando ciclos preciosos de CPU do servidor.
  2. Use Gameplay Tags para Consultas em Lote: Evite rastrear cada actor dinâmico em um único array global do Verse. Em vez disso, atribua gameplay tags às suas props do scene graph. Use o sistema de consulta de tags do UEFN para encontrar e dar refresh em props dinamicamente com base em limites espaciais.
  3. Desacople a Colisão dos Actors Dinâmicos: Se um objeto não precisa se mover, use uma caixa de colisão invisível estática e pré-posicionada no layout do seu mapa. Anexe apenas o mesh visual e o componente de interação do Verse à entidade dinâmica do scene graph. Isso garante que os jogadores nunca colidam com paredes de colisão invisíveis quando a replicação falhar.
  4. Faça o Offload do Gerenciamento de Estado para a Nuvem: Para qualquer jogo com mecânicas de construção persistentes, implemente uma integração de backend como o horizOn logo no início do desenvolvimento. Armazenar dados de coordenadas na nuvem evita estouros no memory budget local e garante que o progresso do jogador seja preservado entre as instâncias do servidor.

Pronto para criar jogos multiplayer persistentes e escaláveis no UEFN sem as dores de cabeça do backend? Cadastre-se gratuitamente no horizOn ou confira a documentação da API para começar.


Source: Scene graph entities are buggy when player leaves render distance from v41.00