Zurück zum Blog

Behebung des Bugs mit verschwindenden UEFN Scene Graph Entities: Ein Verse-Guide für persistente World States

Veröffentlicht am 13. Juni 2026
Behebung des Bugs mit verschwindenden UEFN Scene Graph Entities: Ein Verse-Guide für persistente World States

Kurz und knapp

Dieser Guide analysiert den UEFN-Replication-Bug, bei dem Scene Graph Entities nach dem Teleportieren der Spieler unsichtbar werden, während ihre Kollisionsdaten erhalten bleiben. Es wird ein pragmatischer Verse-Workaround vorgestellt, der durch gezieltes Triggern von Sichtbarkeits-Updates ein erneutes Replikations-Spawning auf dem Client erzwingt. Da Verse-basierte Ansätze bei großen Datenmengen an Speicher- und Persistenzgrenzen stoßen, wird die Auslagerung des World-State-Managements an einen externen Backend-Dienst wie horizOn als langfristige Lösung empfohlen.

Wenn Ihre Spieler über eine UEFN-Map teleportieren, kehren sie wahrscheinlich zurück und stellen fest, dass ihre sorgfältig platzierten Möbel unsichtbar und nicht interaktiv sind, sie aber dennoch wie Geisterwände physisch blockieren. Dieser frustrierende Replication-Bug plagt Entwickler, die Sandbox-, Tycoon- oder bauorientierte Spiele in UEFN entwickeln, seit dem Update v41.00. Sie bauen ein System, das es Spielern ermöglicht, Möbel, Arcade-Automaten oder dekorative Meshes mithilfe von Scene Graph Entities individuell zu platzieren, und alles funktioniert unter lokalen Bedingungen einwandfrei. Doch in dem Moment, in dem ein Spieler sich zu einem Minispiel, einer Lobby oder einem separaten Bereich der Insel teleportiert und dann zurückkehrt, verschwinden die Meshes.

Was dieses Problem besonders rätselhaft macht, ist das Verhalten der Kollisionsdaten. Der Spieler kann dorthin gehen, wo sich das Objekt befand, gegen eine unsichtbare Wand stoßen und auf einem Stuhl oder Schrank stehen, der eigentlich solide sein sollte. Aber er kann ihn nicht sehen, und seine Verse-gesteuerten Interaktionskomponenten weigern sich zu triggern. Das visuelle Modell ist verschwunden, während die Physik-Repräsentation weiterhin im World-Partition-Grid verankert bleibt.

Um zu verstehen, warum das passiert, müssen wir einen Blick unter die Haube werfen und uns ansehen, wie UEFN Speicher und Replikation verwaltet. Standard-Unreal Engine-Actors nutzen den veralteten Network Replication Graph, um basierend auf Kameraabstand und Relevanz zu bestimmen, was auf dem Bildschirm des Spielers gerendert werden soll. Mit der Einführung des neuen Scene-Graph-Systems in UEFN versuchte Epic Games, das komponentenbasierte Spieldesign zu rationalisieren. Dies führte jedoch zu einer Diskrepanz zwischen der Art und Weise, wie der Server dynamische Entity-Strukturen cacht, und wie der Client visuelle Assets in den GPU-Speicher lädt und wieder daraus entfernt.

Under the Hood: Die Funktionsweise von UEFN Scene Graph Culling

Wenn sich ein Spieler innerhalb der Standard-Renderdistanz einer Entity befindet – typischerweise etwa 15.000 bis 20.000 Unreal Units (150–200 Meter) –, sendet der Replication Graph des Servers aktiv Koordinaten-, Mesh- und Komponentenstatus-Updates an den Client. Der Client verarbeitet diese Netzwerkpakete und rendert die visuellen Mesh-Komponenten entsprechend.

In dem Moment, in dem der Spieler wegteleportiert, passieren mehrere Dinge in schneller Folge:

  • Relevancy Cutoff: Der Viewport des Clients verschiebt sich augenblicklich um Tausende von Einheiten. Der Replication Graph markiert die ursprünglichen Scene Graph Entities als "out of relevancy".
  • GPU Garbage Collection: Um hohe Frameraten auf leistungsschwächeren Geräten wie Mobilgeräten und Konsolen aufrechtzuerhalten, entlädt der Client sofort die visuellen Repräsentationen dieser Out-of-Relevancy-Entities und löscht die statischen Mesh-Komponenten aus dem GPU-Speicher.
  • Collision Caching: Im Gegensatz zu visuellen Meshes wird die Kollisionsgeometrie von der Chaos Physics Engine verwaltet. Physikstrukturen werden in grobe räumliche Blöcke (HLOD-Kollisionsgrids) gruppiert oder lokal auf dem Client gecacht, um zu verhindern, dass der Spieler bei Netzwerkspitzen durch den Boden fällt. Aus diesem Grund bleibt die Kollision intakt, selbst wenn das visuelle Mesh gecullt wird.

Wenn der Spieler zu den ursprünglichen Koordinaten zurückteleportiert, erwartet der Client eine Handshake-Sequenz, um die visuellen Komponenten wiederherzustellen. Aufgrund eines Replication-Bugs im Scene-Graph-Network-Layer geht der Server jedoch davon aus, dass der Client den gecachten visuellen Zustand der Entities immer noch besitzt. Da der Server glaubt, dass der Client die Entities hat, sendet er keine Replication-Spawn-RPCs. Dem Client bleibt ein Physik-Kollisionsblock, aber kein visuelles Mesh und keine aktive Verbindung zu den Interaktionskomponenten.

Wenn ein anderer Spieler in dem Bereich bleibt, während der erste Spieler wegteleportiert, hält der Server den Replication Channel für diese Entities aktiv. Wenn der erste Spieler zurückkehrt, kann er die Objekte immer noch sehen, da der active Replication Stream für den zweiten Spieler den Server zwingt, weiterhin Updates an alle verbundenen Clients zu senden. Sobald jedoch beide Spieler den Bereich verlassen und zurückkehren, bricht der Zustand für alle zusammen.

Technical Deep-Dive: Warum der Replication Graph beim Wiedereintritt fehlschlägt

Der Netzwerkcode der Unreal Engine stützt sich auf ein striktes Network-Relevancy-System. In Multiplayer-Umgebungen kann der Server nicht jeden Actor für jeden Spieler replizieren. Dies würde die Bandbreite des Clients überlasten und den Server-Thread zum Absturz bringen. Stattdessen verwendet der Server einen UReplicationGraph, um Actors in räumliche Grid-Zellen einzuteilen.

Für statische, vorab in einer Map platzierte Actors nutzt UEFN World Partition, um Meshes auf dem Client zu streamen. Dynamische Entities hingegen, die zur Laufzeit über Verse gespawnt oder von Spielern platziert werden, existieren in einem transienten Zustand. Diese transienten Entities profitieren nicht von den statischen, vorkompilierten Streaming-Grids. Stattdessen verlassen sie sich auf dynamische Net-Relevancy-Prüfungen.

Wenn ein Spieler teleportiert, erfährt seine Netzwerkverbindung eine erhebliche Zustandsänderung. Der Server muss die Replication Channels für den alten Standort abbauen und Channels für den neuen Standort aufbauen. Dieser schnelle Wechsel kann zu Konflikten bei der Paketpriorisierung führen. Der Server priorisiert sich schnell bewegende Gameplay-Elemente (wie Spielerposition, Bewegungsgeschwindigkeit und das Abfeuern von Waffen) gegenüber statischen visuellen Komponenten.

Diese Netzwerküberlastung führt häufig zu verworfenen Zustandspaketen, ähnlich den Synchronisationsproblemen, die wir in unserem Guide über wie man Spielerpositions-Desync im UEFN- und Unreal Engine-Multiplayer behebt im Detail beschreiben. Im Fall des Scene-Graph-Bugs scheitert der Replication Graph des Servers daran, die Relevanz der gecullten Entities für den zurückkehrenden Spieler neu zu bewerten. Da der Server nicht registriert, dass dem Client die Entity fehlt, überspringt er das Senden der erforderlichen Property-Updates. Die clientseitige Entity verharrt in einem „Zombie“-Zustand: lebendig im Physik-Thread, aber tot im Rendering- und Interaktions-Thread.

Behebung des Bugs: Ein Verse-basierter Workaround zur Replikationsaktualisierung

Um den uefn scene graph entities disappearing-Bug zu beheben, müssen wir den Server zwingen, diese Entities als "dirty" zu markieren, wenn ein Spieler wieder in Reichweite teleportiert. Indem wir ein Property-Update erzwingen, veranlassen wir den Replication Graph, ein frisches Zustandspaket an den zurückkehrenden Client zu senden, was das Mesh und die interaktiven Komponenten rekonstruiert.

Der robusteste Weg, dies zu erreichen, ist die Erstellung eines dynamischen Entity-Managers in Verse. Dieser Manager trackt Spielerpositionen, erkennt Teleportations-Events (plötzliche Koordinatenänderungen) und führt einen lokalisierten Sichtbarkeits- oder Transform-Toggle für nahegelegene Entities aus, um einen Netzwerk-Refresh zu erzwingen.

Nachfolgend finden Sie ein vollständiges, syntaktisch korrektes Verse-Skript, das dieses Muster zur Zustandsrekonstruktion implementiert.

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

Funktionsweise des Codes

Das replication_refresher_device arbeitet mit einem asynchronen Loop, der die Spielerpositionen abfragt. Durch den Vergleich des aktuellen Koordinatenvektors des Spielers mit seinem Koordinatenvektor von vor 0,5 Sekunden kann das Skript zwischen normaler Fortbewegung und High-Speed-Teleportation unterscheiden.

Wenn ein Teleportations-Event ausgelöst wird, wertet die Funktion HandlePlayerTeleport alle registrierten dynamischen Props aus. Für jedes Prop innerhalb eines Radius von 200 Metern um die neue Position des Spielers ruft sie ForceEntityReplicationRefresh auf. Das Hin- und Herschalten von Hide() und Show() auf dem creative_prop zwingt den Server, die Net-Dirty-Flags auf dem zugrunde liegenden C++-Actor zu aktualisieren. Dies zwingt den Replication Graph, die vollständige Actor-Payload an das Gerät des Clients zu pushen, wodurch die gecullten statischen Meshes und interaktiven Kanäle erfolgreich wieder aufgebaut werden.

Konzeptionierung persistenter World States: Die Grenzen des Verse-Speichers

Während der Verse-Workaround das Rendering-Problem für kleine bis mittlere Maps löst, offenbart er eine tiefere Einschränkung der Runtime-Architektur von UEFN. Wenn Ihr Spiel auf Hunderten von Spielern platzierten Items basiert, kann das Tracking all dieser Objekte in Verse-Arrays das Speicherbudget Ihres Servers schnell erschöpfen.

Jede dynamische Klasseninstanz, jede Vektorkoordinate und jede Variablen-Trackingstruktur in Verse verbraucht Runtime-Speicher. Fortnite-Server laufen unter strengen Ressourcenbeschränkungen. Wenn Sie die maximal zulässige Anzahl an Instruktionen oder die Speicherlimits überschreiten, wird Ihr Server erhebliche Performance-Einbußen erleiden oder komplett abstürzen. Dies führt zu Network-Driver-Timeouts, die Spieler zurück in die Lobby werfen.

Zudem sind Verse-Variablen nicht persistent, wenn eine Serverinstanz in den Ruhezustand übergeht (hiberniert) oder heruntergefahren wird. Wenn ein Server inaktiv wird, weil alle Spieler ihn verlassen haben (ein Prozess, den wir in unserem Deep-Dive über den UEFN-Server-Performance-Exploit analysieren), geht das gesamte Layout der von Spielern platzierten Arcade-Automaten und Möbel dauerhaft verloren.

Um ein wirklich persistentes Spiel zu entwickeln, müssen Sie diese räumlichen Layoutdaten auf einem externen Backend speichern. Dies selbst einzurichten, ist ein gewaltiges Entwicklungsprojekt:

  1. Infrastruktur-Bereitstellung: Sie müssen eine Datenbank (z. B. PostgreSQL oder MongoDB) hosten, die auf Tausende von gleichzeitigen Lese- und Schreibvorgängen skalieren kann.
  2. API-Gateway: Sie müssen einen sicheren HTTP-Server erstellen, der Verse-Anfragen in Datenbankabfragen übersetzt, die Spieler-Authentifizierung übernimmt und das Rate Limiting verwaltet.
  3. Netzwerk-Resilienz: Die HTTP-Clients von Verse sind stark eingeschränkt und bieten keine automatischen Retry-Policies oder Connection Pooling. Sie müssen komplexen Boilerplate-Code schreiben, um Paketverlust und Server-Timeouts zu handhaben.

Für ein kleines Indie-Team ist es eine enorme Ressourcenverschwendung, 4 bis 6 Wochen mit dem Schreiben von Datenbank-Integrationscode zu verbringen, anstatt das Gameplay zu polieren.

Nahtlose Persistenz: Wie horizOn die Zustandssynchronisation löst

Hier kommt horizOn ins Spiel. Als dedizierter Backend-as-a-Service, der speziell für Spielentwickler konzipiert wurde, bietet horizOn einen vorkonfigurierten, latenzarmen Speicher für räumliche Zustände, der sich direkt in UEFN und Verse integrieren lässt.

Anstatt Hunderte von aktiven Scene Graph Entities ständig in der Speicherblase des Servers geladen zu halten, können Sie deren räumliche Koordinaten, Rotationen und benutzerdefinierte Attribute in der Cloud-Datenbank von horizOn speichern. Wenn ein Spieler wegteleportiert, können Sie die lokalen Entities sicher despawnen, um Speicher auf dem Fortnite-Server freizugeben. Kehrt der Spieler zurück, fragen Sie die Datenbank ab und rekonstruieren nur die Entities in seiner unmittelbaren Umgebung.

Durch die Nutzung von horizOn lösen Sie den Culling-Bug und das Problem mit dem Server-Speicher gleichzeitig. Der Server repliziert nur das, was der Spieler aktiv sehen kann, was die Netzwerkbandbreite von ca. 120 KB/s auf weniger als 25 KB/s pro Client reduziert. Wenn eine Serverinstanz hiberniert oder neu startet, wird der Spielfortschritt sofort aus der Cloud wiederhergestellt.

Die Integration von horizOn erfordert nur wenige Zeilen Verse-Code unter Verwendung von Standard-HTTP-Anfragen:

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

Da horizOn die Infrastruktur übernimmt, erhalten Sie eine robuste Datenpersistenz und optimale Server-Performance, ohne eine einzige Zeile Backend-Servercode verwalten zu müssen.

Best Practices für die Verwaltung der UEFN Scene Graph Replication

Wenn Sie derzeit ein Sandbox- oder Tycoon-Spiel in UEFN entwickeln, wenden Sie diese vier praktischen Tipps an, um sicherzustellen, dass Ihre World States synchronisiert bleiben und Ihre Server effizient laufen:

  1. Implementieren Sie dynamisches Spawnen und Despawnen: Halten Sie nicht Hunderte von interaktiven Objekten gleichzeitig in der Welt geladen. Schreiben Sie ein Verse-Skript, das Meshes nur dann spawnt, wenn ein Spieler in der Nähe ist, und sie despawnt, wenn er geht – das spart wertvolle CPU-Zyklen des Servers.
  2. Nutzen Sie Gameplay Tags für Batch-Abfragen: Vermeiden Sie es, jeden dynamischen Actor in einem einzigen globalen Verse-Array zu tracken. Weisen Sie Ihren Scene Graph Props stattdessen Gameplay Tags zu. Nutzen Sie das Tag-Abfragesystem von UEFN, um Props dynamisch basierend auf räumlichen Grenzen zu finden und zu aktualisieren.
  3. Entkoppeln Sie Kollision von dynamischen Actors: Wenn sich ein Objekt nicht bewegen muss, verwenden Sie eine statische, vorab platzierte unsichtbare Kollisionsbox in Ihrem Map-Layout. Fügen Sie das visuelle Mesh und die Verse-Interaktionskomponente nur der dynamischen Scene Graph Entity hinzu. Dies garantiert, dass Spieler bei fehlgeschlagener Replikation niemals auf unsichtbare Kollisionswände stoßen.
  4. Lagern Sie das State Management in die Cloud aus: Implementieren Sie bei jedem Spiel mit persistenten Baumechaniken frühzeitig in der Entwicklung eine Backend-Integration wie horizOn. Das Speichern von Koordinatendaten in der Cloud verhindert Speicherbudget-Überläufe auf dem Server und stellt sicher, dass der Spielfortschritt über Serverinstanzen hinweg erhalten bleibt.

Bereit, skalierbare, persistente Multiplayer-Spiele in UEFN ohne Backend-Kopfschmerzen zu entwickeln? Registrieren Sie sich kostenlos bei horizOn oder werfen Sie einen Blick in die API-Dokumentation, um loszulegen.


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