İçeriğe geç
SEYIL Studios
Blog

Unity'de Sahne Yükleme: Additive mi, Tek Sahne mi?

Unity'de sahne mimarisi kurarken additive yükleme ne zaman işi kolaylaştırır, ne zaman referans karmaşasına dönüşür? Kod örnekleri ve tipik hata senaryolarıyla pratik bir karar rehberi.

IIlhan Seyhan6 dk okuma0 görüntülenme
Unity'de Sahne Yükleme: Additive mi, Tek Sahne mi?

Unity'de yeni bir projeye başlarken en erken verilen ve en geç fark edilen kararlardan biri sahne mimarisi: her şey tek sahnede mi duracak, yoksa sahneler additive olarak üst üste mi yüklenecek? Bu yazıda iki yaklaşımın nerede işe yaradığını, additive yüklemenin hangi noktada referans karmaşasına dönüştüğünü ve küçük bir projede sahneleri nasıl bölmenin mantıklı olduğunu çalışan kod örnekleriyle anlatıyoruz.

İki yaklaşım arasındaki gerçek fark

LoadSceneMode.Single ile bir sahne yüklediğinizde mevcut sahneler boşaltılır; Unity belgelerine göre LoadSceneMode.Additive ise sahneyi yüklü olanları boşaltmadan ekler ve Hierarchy penceresinde ikinci bir kök olarak görünür. Teknik fark bu kadar basit. Mimari fark ise şu: single modda sahne bir uygulama durumudur, additive modda sahne bir içerik parçasıdır.

Bu ayrımı netleştirmeden additive'e geçmek en yaygın hata. Sahneyi "ekranın kendisi" olarak düşünmeye devam edip bir de üst üste yüklerseniz, iki sahnenin de kendi kamerasını, kendi AudioListener'ını ve kendi "oyun yöneticisini" taşıdığı bir yapı çıkar ortaya. Konsolda görünen ilk uyarı da genellikle birden fazla AudioListener olur.

Tek sahne ne zaman yeterli?

Ekran sayısı azsa ve ekranlar arasında ağır içerik yüklemesi yoksa tek sahne fazlasıyla yeterlidir. Kart Üçlüsü gibi tek tahta üzerinde geçen gündelik kart oyunlarında menü, oyun ve sonuç ekranı çoğu zaman aynı sahnede duran UI panelleridir; sahne değiştirmek yerine panelleri açıp kapatmak hem daha hızlıdır hem de durum aktarımı derdini tamamen ortadan kaldırır.

Tek sahnenin sınırı şu üç işaretle belli olur:

  • Sahne dosyası artık iki kişinin aynı anda dokunamayacağı kadar büyükse (versiyon kontrolünde her PR'da .unity dosyası çakışıyorsa),
  • Hierarchy'de "şu an hangi ekran aktif" sorusunu cevaplamak için birden fazla objenin SetActive durumuna bakmak gerekiyorsa,
  • Açılışta hiç kullanılmayacak içerikler de belleğe giriyorsa.

İlk madde küçük ekiplerde sanılandan çok daha erken devreye girer. Sahne dosyalarının birleştirilmesi pratikte mümkün olmadığı için, bölünme kararı çoğu zaman teknik değil ekip kaynaklı olur.

Additive yüklemenin işi kolaylaştırdığı yer

Additive yükleme, ömrü farklı olan içerikleri ayırmak istediğinizde kazandırır. Tipik bölünme şöyle:

  • Bootstrap: uygulama boyunca yaşayan servisler (ses, kayıt, analitik, sahne yöneticisi). Bir kez yüklenir, hiç boşaltılmaz.
  • UI: kalıcı arayüz katmanı, Canvas ve geçiş perdesi.
  • Level/Content: sürekli değişen, yüklenip boşaltılan asıl içerik.

Kule savunma gibi aynı sistem üzerinde farklı haritaların koştuğu türlerde bu ayrım doğrudan işe yarar: Thornguard: Tower Defense tipi bir yapıda kule mantığı, dalga sistemi ve arayüz her haritada aynıdır; değişen sadece zemin, yol ve dalga verisidir. Ortak kısmı sahneden sahneye kopyalamak yerine kalıcı bir katmanda tutmak, hem kurulum süresini hem de "bir haritada düzeltilen bug'ın diğerinde durması" sorununu bitirir. Paylaşılan kod ve sahne katmanlarının nerede sınıra dayandığını iki oyun bir kod tabanı yazısında ayrıca ele almıştık.

Çalışan bir sahne geçişi iskeleti

Aşağıdaki bileşen Bootstrap sahnesinde durur ve içerik sahnelerini sırayla değiştirir. SceneManager.LoadScene yerine asenkron sürümü kullanıyoruz; Unity belgeleri senkron LoadScene çağrısının yarı asenkron davrandığını ve kare takılmasına yol açabileceğini belirtiyor.

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneFlow : MonoBehaviour
{
    private string currentContent;

    public IEnumerator SwitchContent(string nextScene)
    {
        if (!string.IsNullOrEmpty(currentContent))
        {
            AsyncOperation unload = SceneManager.UnloadSceneAsync(currentContent);
            while (!unload.isDone) yield return null;
        }

        AsyncOperation load = SceneManager.LoadSceneAsync(nextScene, LoadSceneMode.Additive);
        load.allowSceneActivation = false;

        while (load.progress < 0.9f) yield return null;

        yield return Resources.UnloadUnusedAssets();

        load.allowSceneActivation = true;
        while (!load.isDone) yield return null;

        Scene loaded = SceneManager.GetSceneByName(nextScene);
        SceneManager.SetActiveScene(loaded);
        currentContent = nextScene;
    }
}

Buradaki allowSceneActivation = false satırı önemli: Unity belgelerinde anlatıldığı gibi bu bayrak sahnenin hazır olduğu anda aktifleşmesini geciktirir, ilerleme değeri 0.9'da bekler ve isDone siz izin verene kadar true olmaz. Geçiş perdesini indirmek, eski içeriğin belleğini toplamak ya da minimum yükleme süresi uygulamak için ihtiyacınız olan pencere tam olarak burası. Bayrağı unutup while (load.progress < 1f) yazarsanız döngü hiç bitmez — bu, additive yüklemede en sık düşülen tuzaklardan biri.

Referans karmaşası tam olarak nerede başlıyor?

Additive yapının bedeli, editörde kolayca kurulan referansların çalışma zamanında kurulamamasıdır. Sahneler arası sürükle-bırak referansı yoktur; UI sahnesindeki bir butonu Level sahnesindeki bir objeye Inspector'dan bağlayamazsınız. Bu duvara çarpınca çoğu ekip FindObjectOfType çağrılarına sarılır ve bir süre sonra hangi sistemin ne zaman hazır olduğu tahmin edilemez hale gelir.

Pratikte işe yarayan üç kural:

  1. Bağımlılık yönü tek olsun. Kalıcı katman içerik sahnesini tanımaz; içerik sahnesi kalıcı katmanı tanır. Ters yön gerekiyorsa event kullanın.
  2. İçerik sahnesinin tek bir giriş noktası olsun. Sahnede LevelRoot gibi tek bir kök bileşen dursun, o kendini kalıcı servise kaydetsin. Sahnedeki diğer her şeye erişim bu kök üzerinden olsun.
  3. Kayıt varsa kayıt silme de olsun. OnEnable'da abone olan her şey OnDisable'da abonelikten çıksın. Sahne boşaltıldığında yok olan bir nesneye ait delegate kalıcı servisin listesinde kalırsa, sonraki yüklemede MissingReferenceException alırsınız.

Sık karşılaşılan dört hata senaryosu

Instantiate ettiğiniz nesne yanlış sahnede doğuyor. Yeni nesneler aktif sahneye eklenir. Aktif sahne hâlâ Bootstrap ise, üretilen düşmanlar içerik sahnesi boşaltıldığında yok olmaz ve ekranda öylece kalır. Ya SetActiveScene ile aktif sahneyi doğru seçin ya da nesneyi taşıyın:

GameObject spawned = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
SceneManager.MoveGameObjectToScene(spawned, SceneManager.GetSceneByName("Level_01"));

Bu API yalnızca kök nesnelerle çalışır; bir başkasının child'ı olan nesneyi taşımadan önce transform.SetParent(null) demeniz gerekir.

Aydınlatma ve skybox içerik sahnesinden gelmiyor. Ortam ışığı, skybox ve fog ayarları aktif sahnenin ayarlarıdır. Additive yüklediğiniz haritanın ışık ayarları uygulanmıyorsa neredeyse her zaman sebep, aktif sahnenin hâlâ Bootstrap olmasıdır.

DontDestroyOnLoad nesneleri ikiye katlanıyor. Editörde içerik sahnesini tek başına çalıştırıp test edebilmek için sahneye bir de "yedek" yönetici koyduysanız, normal akışta iki tane olur. Çözüm, yöneticiyi sahneye koymak yerine Bootstrap'ten türetmek ve içerik sahnesinden çalıştırma ihtiyacını editör betiğiyle karşılamak.

Boşaltma yapıyorsunuz ama bellek düşmüyor. UnloadSceneAsync sahnedeki nesneleri yok eder, o sahnenin kullandığı asset'leri otomatik olarak bellekten atmaz. Doku ve mesh belleğinin düşmesi için Resources.UnloadUnusedAssets() çağrısının tamamlanmasını beklemek gerekir; yukarıdaki iskelette bu yüzden yield return ile bekliyoruz. Yükleme sırasındaki kare düşüşlerini ölçerken profiler'ı doğru okumak da ayrı bir konu — mobilde kare süresi yazısındaki yöntem burada da geçerli.

Küçük bir projede sahneleri nasıl bölmeli?

Pratik cevap: iki sahneyle başlayın. Bir Bootstrap (kalıcı servisler + kalıcı UI kökü), bir de içerik sahnesi. Bu bölünme neredeyse hiçbir karmaşıklık getirmez ama ileride üçüncü, dördüncü içerik sahnesini eklemenizi bedava hale getirir. Menü ekranlarını ayrı sahne yapmayın; onlar UI katmanında panel olarak kalsın.

Sahne sayısını artırmak için somut bir gerekçe arayın: içerik gerçekten büyükse, versiyon kontrolünde çakışıyorsa ya da bellekten atılması gerekiyorsa bölün. "Daha düzenli görünsün" gerekçesiyle beş sahneye çıkmak, kazandığı düzenden fazlasını hata ayıklama süresi olarak geri alır. Sahneleri bölmek geri alınabilir bir karar; ama bölünmüş bir yapıda kurulan gevşek referans ağını sökmek çok daha pahalıdır.

Paylaş:

Yorumlar

İlk yorumu siz yapın.