Unity’de Bir Objenin Sahneler Arası Geçişlerde Yok Olmasını Önlemek + Singleton Prensibi

Yayınlandı: 03 Ağustos 2014 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

GÜNCELLEME (01.09.2018): Kodlar C#’a çevrildi ve OnLevelWasLoaded fonksiyonu, SceneManager fonksiyonları ile değiştirildi.

Hepinize merhaba,

Bu dersimizde, Unity 3D‘de bir objenin sahneler (scene) arası geçiş yapmasını ve ne olursa olsun o objeden aynı anda sadece bir tane olmasını nasıl sağlayacağınızı göreceğiz.

Örneğin oyununuz çok zor bir runner oyunu ve oyuncu ortalama 5-10 saniyede bir ölüp duruyor. Oyuncu ölünce Application.LoadLevel ile bölüme restart atıyorsunuz diyelim. Eğer oyununuzun bir arkaplan müziği varsa her restart atışınızda bu müzik başa saracaktır ve kısa bir süre sonra bu oyuncuyu gıcık edecektir. Onun yerine oyuncu ölüp bölüme restart atılsa bile müziğin kaldığı yerden devam etmesini istiyorsunuz diyelim. İşte burada iki prensip bir arada kullanılmakta:

1- Müzik objesi scene’ler arası geçişte yok olmuyor, böylece müzik kaldığı yerden çalmaya devam ediyor

2- Bölüme restart atınca müzik objesinden elinizde iki tane oluyor: birisi önceki scene’den gelen ve çalmaya devam eden müzik objesi, öteki ise scene’e restart atınca sıfırdan oluşan müzik objesi. Bu sıfırdan oluşan müzik objesinin çalmasını istemiyoruz (singleton prensibi)

Anlayacağınız üzere, singleton prensibi bir objeden aynı anda sadece bir tane olmasını (ve bu objenin de scene’ler arası geçiş yaparken bizimle gelen obje olmasını) sağlar. Saydığım bu iki özelliği de oyununuzda uygulamak çok basit.

SingletonMuzik adında yeni bir C# script oluşturup bunu müzik objenize verin ve ardından scripti şöyle değiştirin:

using UnityEngine;
 
public class SingletonMuzik : MonoBehaviour
{
	private static SingletonMuzik obje = null;
 
	void Awake()
	{
		if( obje == null )
		{
			obje = this;
			DontDestroyOnLoad( this );
		}
		else if( this != obje )
		{
			Destroy( gameObject );
		}
	}
}

Artık levela restart attığınızda müzik kaldığı yerden çalmaya devam edecek. Harika!

Peki bu birkaç satırlık script nasıl oluyor da bu işi başarıyor? Şöyle ki, elimizde SingletonMuzik türünde static bir değişken var: “obje“. Bu static değişkenin ilk değeri “null“. “obje” değişkenimizin görevi, scene’ler arası geçiş yapan müzik objesindeki SingletonMuzik scriptini depolamak. Static değişkenlerde şöyle bir şey var ki bir static değişkenin ilk değerini alması sadece bir kere gerçekleşir. Yani “obje” değişkeni, script ilk kez çalıştırıldığında null değeri alır ama sonradan levela restart atıp scriptin yeniden çalıştırılmasını sağlasak da “obje”nin değeri tekrar otomatik olarak null’a ayarlanmaz!

Awake fonksiyonu scene açıldığında tek seferlik çalıştırılır, tıpkı Start fonksiyonu gibi. Tek fark, Awake fonksiyonu Start fonksiyonundan daha önce çalıştırılır. Bu fonksiyonda “obje“nin değerinin null olup olmadığına bakıyoruz. Eğer null ise, bunun anlamı henüz bizimle scene’ler arası geçiş yapan bir müzik objesi yok demektir (bir başka deyişle oyunu yeni başlatmışız demektir). Bu durumda obje’ye değer olarak scriptin kendisini (this) veriyoruz ve bu scriptin atandığı müzik objesinin scene’ler arası geçişlerde yok olmamasını sağlıyoruz: “DontDestroyOnLoad(this);“.

Oyuna restart atınca elimizde iki müzik objesi oluyor: bizimle scene’ler arası geçiş yapan obje ve scene açıldığında sıfırdan oluşturulan müzik objesi. Biz sıfırdan oluşturulan müzik objesinin çalmasını istemiyoruz. Neyse ki scriptin Awake fonksiyonundaki “else if” koşulu bunu bizim için yapıyor: müzik objesindeki SingletonMuzik scriptinin (this), “obje” değişkeninde depolanan script ile aynı olup olmadığına bakıyoruz. Hayır, aynı değil çünkü “obje” değişkeninde depolanan SingletonMuzik scripti, bizimle scene’ler arası geçiş yapan müzik objesindeki SingletonMuzik scripti. Bu durumda scene açıldığında yeniden oluşturulan müzik objesini “Destroy( gameObject );” fonksiyonu yardımıyla siliyoruz ve elimizde sadece scene’ler arası geçiş yapan müzik objesi kalmış oluyor.

Tek bir şeye dikkat etmeniz lazım: müzik objesi scene’ler arası geçiş yaptığı için eğer ki menüye geçiş yaparsanız müzik de sizinle beraber menüye gelecek ve burada da çalmaya devam edecektir. Bunu önlemek ve menüye dönünce müzik objesinin yok olmasını sağlamak için “SingletonMuzik“i şuna benzer bir şekilde değiştirebilirsiniz:

using UnityEngine;
using UnityEngine.SceneManagement;
 
public class SingletonMuzik : MonoBehaviour
{
	private static SingletonMuzik obje = null;
	
	void Awake()
	{
		if( obje == null )
		{
			obje = this;
			DontDestroyOnLoad( this );
			
			SceneManager.sceneLoaded += SahneYuklendi;
		}
		else if( this != obje )
		{
			Destroy( gameObject );
		}
	}
	
	void OnDestroy()
	{
		SceneManager.sceneLoaded -= SahneYuklendi;
	}
	
	// Yeni bir sahne yüklenince çağrılır
	void SahneYuklendi( Scene scene, LoadSceneMode mode )
	{
		if( scene.name == "AnaMenu" )
		{
			obje = null;
			Destroy( gameObject );
		}
	}
}

SceneManager.sceneLoaded += SahneYuklendi;” kodu vasıtasıyla, yeni bir scene yüklenince “SahneYuklendi” fonksiyonunu çağırıyoruz. Bu fonksiyonda, geçiş yapılan scene’in adının “AnaMenu” olup olmadığına bakıyoruz ve eğer öyleyse müzik objesini yok ediyoruz. Obje yok olmadan hemen önce ise (OnDestroy), “SceneManager.sceneLoaded -= SahneYuklendi;” kodu ile objedeki “SahneYuklendi” fonksiyonunun bir daha çağrılmamasını sağlıyoruz. Daha fazla bilgi için: https://yasirkula.com/2019/10/29/unity-c-delegate-ve-eventler/

Umarım ders faydalı olur; başka derslerde görüşmek üzere!

yorum
  1. Gülnur dedi ki:

    Merhaba, ben sahnemdeki objeyi bir buton yardımıyla AR a aktarmak istiyorum. Fakat sahnemde bir sürü objem var, sadece aktif olan objenin aktarılması gerek. Nasıl yapabilirim?

    • yasirkula dedi ki:

      Kaçınmak istediğiniz şey diğer tüm objeleri tek tek kapatmak ise, AR sahnesindeyken kameranın Culling Flags’ini sadece belli bir layer’a alıp, aktif olan objenin ve varsa child’larının gameObject.layer’larını da Culling Flags’e verdiğiniz layer’a ayarlayabilirsiniz. Kamera sadece Culling Flags layer’ındaki objeleri render alır.

  2. Giray dedi ki:

    Öncelikle merhaba hocam,
    ben müziğin bir sahneye gelince yok olmasını değil de sadece o sahne için durmasını istiyorum sonraki sahnede kaldığı yerden devam etmesini ama yok olduğu için sonraki sahnede oynatmaya çalışınca destroyed uyarısı veriyor bunu nasıl halledebilirim şimdiden teşekkürler.

    • yasirkula dedi ki:

      Awake fonksiyonunda UnityEngine.SceneManagement.SceneManager.sceneLoaded event’ine kaydolabilirsiniz. Bu event’te, yüklenen sahnenin ismine erişebilirsiniz. Ardından sahnenin ismine göre müziği kapatıp açabilirsiniz. Bu event sadece sahne değişikliklerinde çalıştığı için, oyunun ilk sahnesinde çağrılmaz bilginiz olsun.

      • Giray dedi ki:

        Eyvallah hocam,
        geç gördüm öncesinde farklı yöntemle hallettim , başka müzik olan sahnelerde muteluyorum diğer sahneye geçişinde muteunu açıyorum.

  3. Murat dedi ki:

    ” “SceneManager.sceneLoaded += SahneYuklendi;” kodu vasıtasıyla, yeni bir scene yüklenince “SahneYuklendi” fonksiyonunu çağırıyoruz.” demişsiniz fakat kod içerisinde “-=” şeklinde görünüyor. Bilgi vermek istedim.

    • yasirkula dedi ki:

      Awake fonksiyonunda += yapıp OnDestroy’da -= yapıyoruz. Böylece, obje yok olduğunda (OnDestroy), artık var olmayan objedeki SahneYuklendi fonksiyonunun çağrılmasının önüne geçiyoruz. Derste özellikle -=’den bahsetmediğim için script’in biraz kafanızı karıştırması normal 🙂 Düzelteceğim.

  4. Haktan dedi ki:

    Bu yöntem asset klasörlerin içindeki veriler içinde geçeri mi hocam ? Yani veri çekince sahne sorunu olur mu ?

    • yasirkula dedi ki:

      Asset’ler herhangi bir sahneye ait değiller o yüzden onlarda DontDestroyOnLoad/Destroy fonksiyonlarını çağırmayın ve onları singleton yapmaya çalışmayın.

  5. Ahmet dedi ki:

    Teşekkür ederim iyiki varsınız 😀

  6. Ahmet dedi ki:

    Yasir hocam ilk etapta Update fonsiyonunun içine yazdığımız kod şu şekildedir dedim ya. Ben zaten sonrasında Update’nin içinde gördüklerimi sildim. Bölüm geçtiğimiz esnada gameObject.Length == 2 mi diye kontrol ediyorum aslında. Bunu yukarıda detaylı belirtmediğimi fark ettim. Şimdi hal böyle iken FindWithTag yöntemini kullanmamın performans açısından bir zararı olur mu. (Tahmini olarak 30 sn’ de bir bölüm geçiyorum)

  7. Ahmet dedi ki:

    Bu arada sorunun cevabı çözüldü. Belki aynı sorunu yaşayanlar olabilir. İlk etapta Update fonksiyonunun içine yazdığımız bir kaç satırlık kod DontDestroyOnLoad’ın içindeki game objeyi bulup onu siliyor. Kod şu şekilde:

    void Update()
    {
    GameObject[] gameObject;
    gameObject = GameObject.FindGameObjectsWithTag(“tetik”);

    if (gameObject.Length == 2)
    {
    Destroy (GameObject.FindWithTag(“tetik”));
    }

    }

    • yasirkula dedi ki:

      Update’te FindGameObjectsWithTag kullanmanızı performans açısından önermem. En kötü 3 farklı SingletonMuzik script’i oluşturup 3 müziğe ayrı bir singleton script verin, böylece Destroy işini singleton script halletsin. Bu script’lerin instance’larını da private’tan public’e çevirerek, script’lerinizden bu müziklere direkt SingletonMuzik.instance şeklinde erişebilirsiniz (FindWithTag’e gerek kalmadan).

  8. Ahmet dedi ki:

    Yasir hocam merhabalar. Oyunumda 3 adet arka plan müziği var. GameObject.FindGameObjectWithTag (“tetik”).GetComponent ().Play (); Stop yöntemleri ile müzikleri sırasıyla açıp kapatabiliyorum. Her birinde farklı tag var. Sahne geçişi sırasında yeni oluşan müziği tekrardan kullanabilmek için yok etmiyorum. iki müziğin üst üste gelmesini engellemek için sahne değişiminde yeni oluşan müziğe false demem yeterli oluyor. Bi saniye sonra true ya çeviriyorum.. İhtiyaç halinde geri kullanmak için DontDestroyOnLoad (transform.root.gameObject); ile beni hazırda bekleyen müziği çaldırabiliyorum. Ama yine de bir takım sorunlar oluyor.

    Yapmak istediğim olay şudur. müzik değişim sırasında DontDestroyOnLoad’ın childine erişip onu yok edebilirsem dünyalar benim olacak. 😀 Bir kaç gündür uğraşıyorum ama nafile

  9. GençDevelop dedi ki:

    Merhaba hocam ben sahnedeki müziği kapatma butonu yapmak istiyorum player prefs olarak nasıl yapabilirim?

  10. Arda dedi ki:

    Merhaba ben yok edilen bir game objecti geri çağrabiliyormuyum yani Destroy(Elmas); dedim diyelim sonra bunu geri çağırmak istiyorum nası yaparım

    • yasirkula dedi ki:

      Maalesef mümkün değil. En basit alternatif, objeyi Destroy etmek yerine SetActive(false) ile inaktif hale getirip, geri çağırmak istediğinizde SetActive(true) ile tekrar aktif yapmak.

  11. Mustafa Efdal dedi ki:

    Hocam selamlar,
    Yakın zamanda unity’de yapımına başladığım oyunda bir hata alıyorum. İkinci level’ı direkt olarak çalıştırdığım zaman herhangi bir problem olmadan oynayabiliyorum, karakter hareket ediyor ve yaptığım tüm kodlamalar çalışıyor. Ancak oyunu en baştan başladığım zaman, main screen’den başlıyor level1’e geçiş yapıyorum oynuyorum bölüm bittikten sonra beni bir sahneye geçiş yapıyorum ve ordan tekrar next level dediğimde ikinci seviyeye geçiyor. sahne açılıyor kodladığım tuşlar çalışıyorum. mouse ile kamerayı kontrol edebiliyorum ancak karakter oynamıyor, hareket etmiyor. Bunun çözümü ne olabilir?

    Kullandığım karakter Unity’nin fpscontroller’ı.

    • yasirkula dedi ki:

      Oyunu ilk bölümden başlatınca konsolda herhangi bir hata alıyor musunuz? Eğer almıyorsanız sıkıntıyı tahmin edebileceğimi sanmıyorum, karakteri hareket ettiren kodu bulup bu kodun hangi objede olduğunu tespit etmeli ve bu objeye ne oluyor gözlemlemelisiniz.

      • Umut dedi ki:

        hocam ben alttaki fonksiyonu kullanarak müzikleri aç kapa yapıyorum oyun açıldıktan sonra sıkıntı yok oyna restart atınca bu sefer sesi aç kapa yapamıyorum sebebi nedir?

        public List ses_muzikler;

        public void ses_muzikler_duzenle(bool deger){
        foreach (AudioSource ses in ses_muzikler) {
        ses.enabled=deger;
        PlayerPrefs.SetInt(“muzik”, (deger ? 1 : 0));

        }

        }

      • Umut dedi ki:

        Hocam yukardaki scr.pti kullanıyom müzik tekrar etmesi için onu yazmayı unutmuşum bu scriptti kaldırınca herzaman oyunu aç kapa yapıyorum lakin bu scriptti ekleyince ilk seferde yapıyorum restart yapınca olmuyor?

      • yasirkula dedi ki:

        SingletonMuzik’e şu kodu ekleyin:

        public static AudioSource[] ses_muzikler = new AudioSource[0];

        void Start()
        {
        ses_muzikler = GetComponentsInChildren();
        }

        Artık ses_muzikler_duzenle fonksiyonunun içerisinde ses_muzikler değil SingletonMuzik.ses_muzikler’i kullanın.

  12. radyodunyasi5656 dedi ki:

    Abi magazadan bi obje alıp yerlestirip oyunu yeniden açınca o objenin orda olmasını anlat sana zahmet ya

    • yasirkula dedi ki:

      En basitinden PlayerPrefs kullanabileceğiniz gibi, bir xml save dosyası oluşturup bunu Application.persistentDataPath’te de tutabilirsiniz. Tabi ikinci yöntem, ilkinden daha karmaşık ama daha güçlü.

Cevap Yazın

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.