Bloga Dön

UEFN Scene Graph Entity'lerinin Kaybolması Hatasını Çözmek: Persistent World States İçin Bir Verse Rehberi

Yayınlanma tarihi 13 Haziran 2026
UEFN Scene Graph Entity'lerinin Kaybolması Hatasını Çözmek: Persistent World States İçin Bir Verse Rehberi

Özet olarak

Bu makale, UEFN v41.00 güncellemesinden sonra ortaya çıkan ve Scene Graph entity'lerinin oyuncu uzaklaştığında kaybolmasıyla sonuçlanan replication hatasını ele almaktadır. Hatanın temel nedeni olan server-client arasındaki durum uyuşmazlığı ve collision verilerinin Chaos Physics motoru ile cache'lenmesi incelenmektedir. Sorunu çözmek için Verse tabanlı bir replication yenileme (refresh) yöntemi sunulmakta ve büyük ölçekli haritalardaki bellek sınırlarını aşmak için horizOn gibi bulut tabanlı backend çözümlerinin entegrasyonu önerilmektedir.

Eğer oyuncularınız bir UEFN haritasında teleport oluyorsa, büyük ihtimalle geri döndüklerinde özenle yerleştirdikleri mobilyaları görünmez, etkileşime girilemez ancak kendilerini fiziksel olarak engelleyen hayalet duvarlar gibi bulacaklardır. Bu sinir bozucu replication hatası, v41.00 güncellemesinden bu yana UEFN üzerinde sandbox, tycoon veya inşa odaklı oyunlar geliştiren yazılımcıların başını ağrıtıyor. Oyuncuların Scene Graph entity'lerini kullanarak mobilyaları, arcade cabinet'leri veya dekoratif mesh'leri özel olarak yerleştirmelerine olanak tanıyan bir sistem oluşturuyorsunuz ve local play koşullarında her şey kusursuz çalışıyor. Ancak, bir oyuncu bir minigame'e, lobiye veya adanın başka bir bölümüne teleport olup geri döndüğü an, mesh'ler ortadan kayboluyor.

Bu sorunu özellikle kafa karıştırıcı hale getiren durum, collision verisinin davranışıdır. Oyuncu, nesnenin olması gerektiği yere doğru yürüyebilir, görünmez bir duvara çarpabilir ve normal şartlarda katı bir sandalye veya dolap olması gereken nesnenin üzerinde durabilir. Ancak nesneyi göremez ve Verse tabanlı interaction component'leri tetiklenmeyi reddeder. Görsel model ortadan kaybolmuştur, fakat fiziksel temsil (physics representation) world partition grid'i içine gömülü (baked) kalmaya devam eder.

Bunun neden gerçekleştiğini anlamak için, UEFN'in bellek ve replication süreçlerini arka planda nasıl yönettiğine bakmamız gerekir. Standart Unreal Engine actor'leri, kamera mesafesi ve relevancy durumuna bağlı olarak oyuncunun ekranında neyin render edilmesi gerektiğini belirlemek için eski network replication graph yapısını kullanır. Epic Games, UEFN'in yeni Scene Graph sistemini sunarak component tabanlı oyun tasarımını modernize etmeyi hedefledi. Ancak bu hamle, server'ın dinamik entity yapılarını cache'leme yöntemi ile client'ın görsel asset'leri GPU belleğine stream in ve out etme şekli arasında bir uyumsuzluk yarattı.

Under the Hood: The Mechanics of UEFN Scene Graph Culling

Bir oyuncu bir entity'nin standart render mesafesi içindeyken—genellikle yaklaşık 15.000 ila 20.000 Unreal Unit (150-200 metre)—server'ın replication graph'ı koordinat, mesh ve component state güncellemelerini aktif olarak client'a broadcast eder. Client bu network packet'lerini işler ve görsel mesh component'lerini buna göre render eder.

Oyuncu teleport olduğu anda, kısa süre içinde şu olaylar sırasıyla gerçekleşir:

  • Relevancy Kesintisi (Relevancy Cutoff): Client'ın viewport'u anında binlerce birim uzağa kayar. Replication graph, orijinal scene graph entity'lerini "out of relevancy" olarak işaretler.
  • GPU Garbage Collection: Mobil cihazlar ve konsollar gibi düşük donanımlı sistemlerde yüksek framerate değerlerini korumak için client, bu out-of-relevancy entity'lerin görsel temsillerini derhal bellekten kaldırır ve static mesh component'lerini GPU belleğinden temizler (purge eder).
  • Collision Önbelleğe Alma (Collision Caching): Görsel mesh'lerin aksine, collision geometrisi Chaos Physics motoru tarafından yönetilir. Physics yapıları, network dalgalanmaları sırasında oyuncunun zeminden aşağı düşmesini önlemek için kaba mekansal bloklar halinde (HLOD collision grid'leri) gruplandırılır veya client tarafında lokal olarak cache'lenir. Görsel mesh cull edilse bile collision'ın bozulmadan kalmasının nedeni budur.

Oyuncu orijinal koordinatlara geri teleport olduğunda, client görsel bileşenleri yeniden oluşturmak için bir handshake dizisi bekler. Ancak Scene Graph network katmanındaki bir replication bug nedeniyle, server client'ın entity'lerin cache'lenmiş görsel durumunu hala koruduğunu varsayar. Server, client'ın entity'lere sahip olduğunu düşündüğünden, replication spawn RPC'lerini göndermez. Sonuç olarak client, fiziksel bir collision bloğu ile baş başa kalır; fakat ortada ne bir görsel mesh ne de interaction component'lerine aktif bir bağlantı vardır.

Eğer ilk oyuncu uzaklaşırken bölgede başka bir oyuncu kalmaya devam ederse, server bu entity'ler için replication channel'ını aktif tutar. İlk oyuncu geri döndüğünde nesneleri hala görebilir, çünkü ikinci oyuncu için aktif olan replication stream'i, server'ı bağlı olan tüm client'lara güncelleme broadcast etmeye devam etmeye zorlar. Ancak, her iki oyuncu da bölgeden ayrılıp geri döndüğünde, durum (state) herkes için bozulur.

Technical Deep-Dive: Why the Replication Graph Fails on Re-entry

Unreal Engine'in network kodu, katı bir network relevancy sistemine dayanır. Multiplayer ortamlarda, server her actor'ü her oyuncuya replicate edemez; aksi takdirde client bandwidth'i doygunluğa ulaşır ve server thread'i çöker. Bunun yerine server, actor'leri mekansal grid hücrelerine (spatial grid cells) bölmek için bir UReplicationGraph kullanır.

Haritada statik olarak önceden yerleştirilmiş actor'ler için UEFN, client tarafında mesh'leri stream etmek için World Partition kullanır. Ancak Verse aracılığıyla spawn edilen veya çalışma zamanında (runtime) oyuncular tarafından yerleştirilen dinamik entity'ler geçici (transient) bir durumda bulunur. Bu transient entity'ler, statik olarak önceden derlenmiş streaming grid'lerinden yararlanamazlar. Bunun yerine dinamik net relevancy kontrollerine güvenirler.

Bir oyuncu teleport olduğunda, network bağlantısı büyük bir durum değişikliğine (state change) uğrar. Server eski konum için replication channel'larını sonlandırmalı (tear down etmeli) ve yeni konum için yeni channel'lar başlatmalıdır (spin up etmeli). Bu hızlı değişim, paket önceliklendirme (packet prioritization) çakışmalarına yol açabilir. Server; oyuncu konumu, hareket hızı ve silah atışı gibi hızlı değişen gameplay elementlerine, statik görsel bileşenlere kıyasla öncelik verir.

Bu network tıkanıklığı, UEFN ve Unreal Engine multiplayer oyunlarda oyuncu konumu desync hatasını düzeltme hakkındaki rehberimizde detaylandırdığımız senkronizasyon sorunlarına benzer şekilde, genellikle kayıp durum paketleriyle (dropped state packets) sonuçlanır. Scene graph hatası durumunda, server'ın replication graph'ı geri dönen oyuncu için cull edilmiş entity'lerin relevancy durumunu yeniden değerlendirmekte başarısız olur. Server client'ın bu entity'e sahip olmadığını algılamadığı için gerekli property güncellemelerini göndermeyi atlar. Client tarafındaki entity bir "zombi" durumunda (zombie state) varlığını sürdürür: physics thread'inde hayatta, ancak rendering ve interaction thread'lerinde ölüdür.

Resolving the Bug: A Verse-Based Replication Refresh Workaround

uefn scene graph entities disappearing hatasını çözmek için, bir oyuncu kapsama alanına geri teleport olduğunda server'ı bu entity'leri "dirty" olarak işaretlemeye zorlamalıyız. Bir property güncellemesini zorlayarak, replication graph'ın geri dönen client'a yeni bir state paketi göndermesini tetikleriz; bu da mesh ve etkileşimli bileşenleri (interactive components) yeniden oluşturur.

Bunu başarmanın en kararlı yolu, Verse içinde dinamik bir entity yöneticisi (entity manager) oluşturmaktır. Bu yönetici oyuncu pozisyonlarını takip eder, teleport olaylarını (ani koordinat değişiklikleri) algılar ve ağ yenilemesini (network refresh) tetiklemek için yakındaki entity'ler üzerinde lokal bir görünürlük (visibility) veya transform geçişi (toggle) gerçekleştirir.

Aşağıda, bu state reconstruction pattern'ini uygulayan, sözdizimsel olarak doğru (syntactically correct) eksiksiz bir Verse betiği yer almaktadır.

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

Kodun Çalışma Mantığı

replication_refresher_device, oyuncu konumlarını poll eden (sorgulayan) asenkron bir döngü (asynchronous loop) çalıştırarak işlev görür. Oyuncunun mevcut koordinat vektörünü 0.5 saniye önceki koordinat vektörüyle karşılaştıran script, normal hareket ile yüksek hızlı teleportation arasındaki farkı ayırt edebilir.

Bir teleportation olayı tetiklendiğinde, HandlePlayerTeleport fonksiyonu kayıtlı tüm dinamik prop'ları değerlendirir. Oyuncunun yeni konumunun 200 metrelik yarıçapı içindeki herhangi bir prop için ForceEntityReplicationRefresh fonksiyonunu çağırır. creative_prop üzerinde Hide() ve Show() işlevlerini arka arkaya çalıştırmak (toggle etmek), server'ı altta yatan C++ actor'ü üzerindeki net-dirty flag'lerini güncellemeye zorlar. Bu da replication graph'ı, actor payload'unun tamamını client cihazına göndermeye (push etmeye) zorlayarak cull edilmiş static mesh'lerin ve etkileşim kanallarının (interactive channels) başarıyla yeniden oluşturulmasını sağlar.

Persistent World States Mimarisi: Verse Memory Sınırları

Verse tabanlı bu geçici çözüm (workaround) küçük ve orta ölçekli haritalar için render sorununu çözse de, UEFN'in runtime mimarisinin daha derin bir sınırlamasını ortaya çıkarır. Oyununuz oyuncular tarafından yerleştirilen yüzlerce nesneye dayanıyorsa, bunların hepsini Verse dizilerinde (arrays) izlemek, server'ınızın bellek bütçesini (memory budget) hızla tüketebilir.

Verse'teki her dinamik sınıf örneği (class instance), vektör koordinatı ve değişken takip yapısı çalışma zamanı belleği (runtime memory) tüketir. Fortnite sunucuları sıkı kaynak kısıtlamaları altında çalışır. İzin verilen maksimum işlem sayısını (instruction counts) veya bellek ayırma sınırlarını (memory allocation limits) aşarsanız, server'ınız ciddi performans kayıpları yaşayabilir veya tamamen çökebilir; bu durum oyuncuları lobiye geri gönderen network driver timeouts hatalarına yol açar.

Dahası, bir server instance'ı hibernate olduğunda veya kapandığında Verse değişkenleri kalıcı (persistent) olmaz. Eğer tüm oyuncular ayrıldığı için bir server inaktif hale gelirse (bu süreç, the UEFN server performance exploit üzerine yaptığımız detaylı analizde ele alınmıştır), oyuncu tarafından yerleştirilen arcade cabinet ve mobilyaların tüm düzeni kalıcı olarak kaybolur.

Gerçek anlamda persistent bir oyun oluşturmak için, bu mekansal yerleşim verilerini (spatial layout data) harici bir backend üzerinde saklamanız gerekir. Bunu kendi başınıza kurmak devasa bir mühendislik işidir:

  1. Altyapı Hazırlığı (Infrastructure Provisioning): Eşzamanlı (concurrent) binlerce okuma ve yazma işlemini kaldırabilecek kapasitede bir veritabanı (örneğin PostgreSQL veya MongoDB) barındırmanız (host etmeniz) gerekir.
  2. API Gateway: Verse isteklerini veritabanı sorgularına çeviren, oyuncu kimlik doğrulamasını (authentication) gerçekleştiren ve rate limiting süreçlerini yöneten güvenli bir HTTP sunucusu oluşturmanız gerekir.
  3. Ağ Dayanıklılığı (Network Resilience): Verse'in HTTP client'ları oldukça kısıtlıdır; otomatik yeniden deneme politikaları (retry policies) veya connection pooling mekanizmaları yoktur. Paket kayıplarını (packet loss) ve server zaman aşımlarını (timeouts) yönetmek için karmaşık boilerplate kodlar yazmanız gerekir.

Küçük bir bağımsız geliştirici (indie) ekibi için, oynanışı (gameplay) geliştirmek yerine 4 ila 6 haftayı veritabanı entegrasyonu (database integration) kodu yazmaya harcamak büyük bir kaynak israfıdır.

Seamless Persistence: horizOn Durum Senkronizasyonunu (State Synchronization) Nasıl Çözüyor?

İşte bu noktada devreye horizOn giriyor. Oyun geliştiricileri için özel olarak tasarlanmış bir Backend-as-a-Service olan horizOn, UEFN ve Verse ile doğrudan entegre olan, önceden yapılandırılmış (pre-configured), düşük gecikmeli (low-latency) mekansal durum depolaması (spatial state storage) sunar.

Server'ın memory bubble'ında her an yüzlerce aktif scene graph entity'sini yüklü tutmak yerine, bunların mekansal koordinatlarını, rotasyonlarını ve özel niteliklerini (custom attributes) horizOn bulut veritabanına kaydedebilirsiniz. Bir oyuncu başka bir yere teleport olduğunda, Fortnite server belleğinde yer açmak için yerel entity'leri güvenle despawn edebilirsiniz. Oyuncu geri teleport olduğunda ise veritabanını sorgulayarak yalnızca yakın çevresindeki entity'leri yeniden oluşturursunuz (reconstruct edersiniz).

horizOn kullanarak hem culling bug'ını hem de server bellek sorununu aynı anda çözersiniz. Server sadece oyuncunun aktif olarak görebildiği şeyleri replicate eder, bu da client başına network bandwidth tüketimini ~120KB/s seviyesinden ~25KB/s'nin altına düşürür. Bir server instance'ı hibernate olduğunda veya yeniden başlatıldığında, oyuncunun ilerlemesi buluttan anında geri yüklenir.

horizOn entegrasyonu, standart HTTP isteklerini kullanan birkaç satır Verse koduyla tamamlanır:

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

horizOn altyapıyı sizin yerinize yönetirken, tek bir satır backend sunucu koduyla uğraşmak zorunda kalmadan güçlü veri kalıcılığı (data persistence) ve optimum sunucu performansı elde edersiniz.

UEFN Scene Graph Replication Süreçlerini Yönetmek İçin En İyi Pratikler

Şu anda UEFN'de bir sandbox veya tycoon oyunu geliştiriyorsanız, dünya durumlarınızın (world states) senkronize kalmasını ve server'larınızın verimli çalışmasını sağlamak için şu dört uygulanabilir ipucunu hayata geçirin:

  1. Dinamik Spawning ve Despawning Uygulayın: Dünyada aynı anda yüzlerce etkileşimli nesneyi yüklü tutmayın. Değerli server CPU döngülerinden tasarruf etmek için, yalnızca bir oyuncu yakınlaştığında mesh'leri spawn eden ve ayrıldıklarında despawn eden bir Verse betiği yazın.
  2. Toplu Sorgulama İçin Gameplay Tag'lerini Kullanın: Her dinamik actor'ü tek bir global Verse array'inde takip etmekten kaçının. Bunun yerine, scene graph prop'larınıza gameplay tag'leri atayın. Prop'ları mekansal sınırlara (spatial bounds) göre dinamik olarak bulmak ve yenilemek için UEFN tag sorgulama sistemini (tag query system) kullanın.
  3. Collision Yapısını Dinamik Actor'lerden Ayırın: Eğer bir nesnenin hareket etmesi gerekmiyorsa, harita düzeninizde statik, önceden yerleştirilmiş görünmez bir collision kutusu kullanın. Dinamik scene graph entity'sine yalnızca görsel mesh'i ve Verse interaction bileşenini ekleyin. Bu sayede, replication başarısız olduğunda oyuncuların görünmez collision duvarlarına çarpmasını engellersiniz.
  4. Durum Yönetimini Buluta Taşıyın: Persistent inşa mekaniklerine sahip herhangi bir oyun için, geliştirme aşamasının başlarında horizOn gibi bir backend entegrasyonu uygulayın. Koordinat verilerini bulutta depolamak, yerel bellek bütçesi aşımlarını (memory budget overflows) önler ve oyuncu ilerlemesinin server instance'ları arasında korunmasını sağlar.

Backend sancıları çekmeden UEFN'de ölçeklenebilir, persistent multiplayer oyunlar geliştirmeye hazır mısınız? Başlamak için ücretsiz olarak horizOn'a kaydolun veya API dökümantasyonuna göz atın.


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