Terug naar Blog

De UEFN Scene Graph Entities Disappearing Bug Oplossen: Een Verse-gids voor Persistent World States

Gepubliceerd op 13 juni 2026
De UEFN Scene Graph Entities Disappearing Bug Oplossen: Een Verse-gids voor Persistent World States

Kort samengevat

Deze gids behandelt de replication-bug in UEFN waarbij Scene Graph-entities onzichtbaar worden wanneer een speler buiten render-afstand teleporteert en weer terugkeert. We leggen de onderliggende oorzaak uit van hoe de server de relevancy-status verkeerd inschat, wat leidt tot een zombie-state met intacte collision maar zonder visuele mesh. Ten slotte bieden we een Verse-script als workaround om replication handmatig te refreshen en bespreken we hoe [horizOn](https://horizon.pm) dit persistentieprobleem en servergeheugen-limieten definitief oplost.

Als je spelers over een UEFN-map teleporteren, keren ze waarschijnlijk terug om te ontdekken dat hun zorgvuldig geplaatste meubels onzichtbaar en non-interactief zijn, maar hen fysiek nog steeds blokkeren als ghost walls. Deze frustrerende replication-bug plaagt developers die sandbox-, tycoon- of building-centric games bouwen in UEFN sinds de v41.00-update. Je bouwt een systeem waarmee spelers meubels, arcadekasten of decoratieve meshes op maat kunnen plaatsen met behulp van Scene Graph-entities, en alles werkt naadloos onder local play-omstandigheden. Echter, op het moment dat een speler teleporteert naar een minigame, een lobby of een ander deel van het eiland en vervolgens terugkeert, verdwijnen de meshes.

Wat dit probleem bijzonder raadselachtig maakt, is het gedrag van de collision-data. De speler kan naar de plek lopen waar het object stond, tegen een onzichtbare muur botsen en bovenop staan wat een stevige stoel of kast zou moeten zijn. Maar ze kunnen het niet zien, en hun Verse-driven interaction-components weigeren te triggeren. Het visuele model is verdwenen, maar de physics-representatie blijft gebakken in het world partition-grid.

Om te begrijpen waarom dit gebeurt, moeten we onder de motorkap kijken naar hoe UEFN geheugen en replication beheert. Standaard Unreal Engine-actors gebruiken de legacy network replication graph om te bepalen wat er op het scherm van een speler gerenderd moet worden op basis van camera-afstand en relevancy. Met de introductie van UEFN's nieuwe Scene Graph-systeem probeerde Epic Games component-based game design te stroomlijnen. Dit zorgde echter voor een mismatch tussen hoe de server dynamische entity-structuren cachet en hoe de client visuele assets in en uit het GPU-geheugen streamt.

Onder de motorkap: de mechanica van UEFN Scene Graph-culling

Wanneer een speler zich binnen de standaard render-afstand van een entity bevindt – typisch rond de 15.000 to 20.000 Unreal Units (150-200 meter) – stuurt de replication graph van de server actief coördinaten-, mesh- en component state-updates naar de client. De client verwerkt deze netwerkpakketten en rendert de visuele mesh-componenten dienovereenkomstig.

Het moment dat de speler wegteleporteert, gebeuren er snel achter elkaar verschillende dingen:

  • Relevancy Cutoff: De viewport van de client verschuift direct duizenden units. De replication graph markeert de originele scene graph-entities als "out of relevancy".
  • GPU Garbage Collection: Om hoge framerates te behouden op lower-end devices zoals mobiel en consoles, ontlaadt de client onmiddellijk de visuele representaties van deze out-of-relevancy entities, waarbij de static mesh-componenten uit het GPU-geheugen worden verwijderd.
  • Collision Caching: In tegenstelling tot visuele meshes wordt collision-geometrie beheerd door de Chaos Physics-engine. Physics-structuren worden gegroepeerd in grove ruimtelijke blokken (HLOD-collision-grids) of lokaal gecachet op de client om te voorkomen dat de speler door de vloer valt tijdens netwerkspikes. Dit is waarom de collision intact blijft, zelfs als de visuele mesh geculled wordt.

Wanneer de speler terugteleporteert naar de oorspronkelijke coördinaten, verwacht de client een handshake-sequentie om de visuele componenten te reconstrueren. Echter, vanwege een replication-bug in de Scene Graph-netwerklaag, neemt de server aan dat de client nog steeds de gecachete visuele status van de entities heeft. Omdat de server denkt dat de client de entities heeft, verstuurt deze de replication spawn-RPC's niet. De client blijft achter met een physics-collision-blok, maar zonder visuele mesh en zonder actieve verbinding met de interaction-components.

Als een andere speler in het gebied blijft terwijl de eerste speler wegteleporteert, houdt de server het replication-kanaal actief voor die entities. Wanneer de eerste speler terugkeert, kan hij de objecten nog steeds zien, omdat de actieve replication-stream voor de tweede speler de server dwingt om updates naar alle verbonden clients te blijven sturen. Echter, zodra beide spelers het gebied verlaten en terugkeren, breekt de status voor iedereen.

Technische deep-dive: Waarom de Replication Graph faalt bij re-entry

De netwerkcode van Unreal Engine vertrouwt op een strikt network relevancy-systeem. In multiplayer-omgevingen kan de server niet elke actor naar elke speler repliceren; dit zou de client-bandbreedte verzadigen en de server-thread laten crashen. In plaats daarvan gebruikt de server een UReplicationGraph om actors in ruimtelijke gridcellen in te delen.

Voor statische, vooraf geplaatste actors in een map gebruikt UEFN World Partition om meshes op de client te streamen. Maar dynamische entities die via Verse zijn gespawnd of tijdens runtime door spelers zijn geplaatst, bevinden zich in een transient state. Deze transient entities profiteren niet van de statische, vooraf gecompileerde streaming-grids. In plaats daarvan vertrouwen ze op dynamische net relevancy-checks.

Wanneer een speler teleporteert, ondergaat diens netwerkverbinding een grote state-wijziging. De server moet de replication-kanalen voor de oude locatie afbreken en kanalen voor de nieuwe locatie opstarten. Deze snelle verschuiving kan leiden tot packet-prioritiseringsconflicten. De server geeft prioriteit aan snelbewegende gameplay-elementen (zoals spelerlocatie, bewegingssnelheid en het afvuren van wapens) boven statische visuele componenten.

Deze netwerkcongestie leidt vaak tot verloren state-packets, vergelijkbaar met de synchronisatieproblemen die we in detail beschrijven in onze gids over hoe je spelerlocatie-desync in UEFN en Unreal Engine multiplayer oplost. In het geval van de scene graph-bug faalt de replication graph van de server om de relevancy van de geculled entities voor de terugkerende speler opnieuw te evalueren. Omdat de server niet registreert dat de client de entity mist, slaat deze het verzenden van de benodigde property-updates over. De client-side entity bevindt zich in een "zombie"-state: levend in de physics-thread, maar dood in de rendering- en interaction-threads.

De bug oplossen: Een op Verse gebaseerde Replication Refresh-workaround

Om de uefn scene graph entities disappearing-bug op te lossen, moeten we de server dwingen deze entities als "dirty" te markeren wanneer een speler weer binnen bereik teleporteert. Door een property-update af te dwingen, triggeren we de replication graph om een nieuw state-packet naar de terugkerende client te sturen, die vervolgens de mesh en interactive-components reconstrueert.

De meest robuuste manier om dit te bereiken is door een dynamische entity-manager in Verse te maken. Deze manager houdt de posities van spelers bij, detecteert teleportatie-events (plotselinge coördinatenwijzigingen) en voert een lokale visibility- of transform-toggle uit op nabijgelegen entities om een netwerk-refresh te forceren.

Hieronder vind je een compleet, syntactisch correct Verse-script dat dit state-reconstruction-patroon implementeert.

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()

Hoe de code werkt

De replication_refresher_device werkt door een asynchrone loop uit te voeren die de locaties van spelers pollt. Door de huidige coördinatenvector van de speler te vergelijken met hun coördinatenvector van 0,5 seconden geleden, kan het script onderscheid maken tussen normale voortbeweging en high-speed teleportatie.

Wanneer een teleportatie-event wordt getriggerd, evalueert de functie HandlePlayerTeleport alle geregistreerde dynamische props. Voor elke prop binnen een straal van 200 meter van de nieuwe positie van de speler, wordt ForceEntityReplicationRefresh aangeroepen. Het toggelen van Hide() en Show() op de creative_prop dwingt de server om de net-dirty-flags op de onderliggende C++-actor bij te werken. Dit dwingt de replication graph om de volledige actor-payload naar het device van de client te pushen, waardoor de geculled static meshes en interactieve kanalen succesvol worden herbouwd.

Persistent World States ontwerpen: De limieten van Verse-geheugen

Hoewel de Verse-workaround het rendering-probleem oplost voor kleine tot middelgrote maps, legt het een diepere beperking bloot van UEFN's runtime-architectuur. Als je game afhankelijk is van honderden door spelers geplaatste items, kan het bijhouden van al deze items in Verse-arrays al snel het geheugenbudget van je server uitputten.

Elke dynamische class-instance, vector-coördinaat en variabele tracking-structuur in Verse verbruikt runtime-geheugen. Fortnite-servers draaien onder strikte resourcebeperkingen. Als je de maximaal toegestane instructie-aantallen of geheugenallocatielimieten overschrijdt, zal je server ernstige performance-degradatie ervaren of volledig crashen, wat leidt tot network driver-timeouts die spelers terugsturen naar de lobby.

Bovendien blijven Verse-variabelen niet behouden wanneer een server-instance in hibernate gaat of afsluit. Als een server inactief wordt omdat alle spelers zijn vertrokken (een proces dat we analyseren in onze deep-dive over de UEFN server-performance-exploit), gaat de volledige lay-out van door spelers geplaatste arcadekasten en meubels permanent verloren.

Om een echt persistente game te bouwen, moet je deze ruimtelijke lay-outdata op een externe backend opslaan. Dit zelf opzetten is een enorme technische onderneming:

  1. Infrastructure Provisioning: Je moet een database hosten (bijv. PostgreSQL of MongoDB) die kan schalen naar duizenden gelijktijdige lees- en schrijfacties.
  2. API Gateway: Je moet een beveiligde HTTP-server bouwen die Verse-requests vertaalt naar database-queries, speler-authenticatie afhandelt en rate limiting beheert.
  3. Network Resilience: De HTTP-clients van Verse zijn zeer beperkt en missen automatische retry-policies of connection-pooling. Je moet complexe boilerplate-code schrijven om packet loss en server-timeouts op te vangen.

Voor een klein indie-team is het besteden van 4 tot 6 weken aan het schrijven van database-integratiecode in plaats van het polijsten van de gameplay een enorme verspilling van resources.

Naadloze persistentie: hoe horizOn state-synchronisatie oplost

Dit is waar horizOn om de hoek komt kijken. Als een dedicated Backend-as-a-Service die specifiek is ontworpen voor game-developers, biedt horizOn vooraf geconfigureerde, low-latency ruimtelijke state-opslag die direct integreert met UEFN en Verse.

In plaats van honderden actieve scene graph-entities te allen tijde in de memory bubble van de server geladen te houden, kun je hun ruimtelijke coördinaten, rotaties en aangepaste attributen opslaan in de cloud-database van horizOn. Wanneer een speler wegteleporteert, kun je de lokale entities veilig despawnen om Fortnite-servergeheugen vrij te maken. Wanneer the speler terugteleporteert, query je de database en reconstrueer je alleen de entities in hun directe omgeving.

Door gebruik te maken van horizOn los je de culling-bug en het servergeheugen-issue tegelijkertijd op. De server repliceert alleen wat de speler actief kan zien, wat de netwerkbandbreedte vermindert van ~120KB/s naar minder dan ~25KB/s per client. Als een server-instance in hibernate gaat of herstart, wordt de voortgang van spelers direct hersteld vanuit de cloud.

Het integreren van horizOn kost slechts een paar regels Verse-code met behulp van standaard HTTP-requests:

# 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}")

Doordat horizOn de infrastructuur afhandelt, krijg je robuuste data-persistentie en optimale server-performance zonder dat je zelf ook maar één regel backend-servercode hoeft te beheren.

Best practices voor het beheren van UEFN Scene Graph-replication

Als je momenteel een sandbox- of tycoon-game bouwt in UEFN, pas dan deze vier praktische tips toe om ervoor te zorgen dat je world states gesynced blijven en je servers efficiënt draaien:

  1. Implementeer dynamisch spawnen en despawnen: Houd niet honderden interactieve objecten tegelijkertijd in de wereld geladen. Schrijf een Verse-script dat meshes alleen spawnt wanneer een speler dichtbij is, en ze despawnt wanneer ze weggaan, wat kostbare server-CPU-cycli bespaart.
  2. Gebruik gameplay-tags voor batch-querying: Vermijd het bijhouden van elke dynamische actor in een enkele globale Verse-array. Wijs in plaats daarvan gameplay-tags toe aan je scene graph-props. Gebruik het UEFN tag-query-systeem om props dynamisch te vinden en te refreshen op basis van spatial bounds.
  3. Ontkoppel collision van dynamische actors: Als een object niet hoeft te bewegen, gebruik dan een statische, vooraf geplaatste onzichtbare collision-box in je map-lay-out. Koppel alleen de visuele mesh en de Verse-interaction-component aan de dynamische scene graph-entity. Dit garandeert dat spelers nooit tegen onzichtbare collision-muren aanlopen wanneer replication faalt.
  4. Verplaats state-management naar de cloud: Implementeer voor elke game met persistente bouwmechanismen vroeg in de ontwikkeling een backend-integratie zoals horizOn. Het opslaan van coördinatendata in de cloud voorkomt lokale memory-budget-overflows en zorgt ervoor dat de voortgang van spelers behouden blijft over verschillende server-instances.

Klaar om schaalbare, persistente multiplayer-games te bouwen in UEFN zonder de backend-hoofdpijn? Meld je gratis aan voor horizOn of bekijk de API-documentatie om aan de slag te gaan.


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