Unity 3D Basit Bir Loading Ekranı Yapımı

Yayınlandı: 03 Ekim 2015 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

SON GÜNCELLEME: 08.08.2019 (Kod C#’a çevrildi)

Hepinize yeniden merhabalar,

Bu dersimizde Unity için basit bir loading ekranı yapacağız. Bu loading ekranı ile scene’ler arası yumuşak bir şekilde geçiş (transition) yapabilecek ve sonraki sahnenin % kaçının yüklendiğini oyuncuya gösterebileceğiz.

O halde son gaz derse başlayalım…

İlk iş yeni bir sahne açalım ve GameObject-UI-Panel yolunu izleyerek sahnede bir panel oluşturalım. Panele “Arkaplan” ismini verelim. Bu paneli loading ekranının arkaplanı için kullanacağız. Bu derste arkaplanımız simsiyah olacak. O halde Panel’in Image component‘inin Color (renk) değerini siyah yapın ve A (alpha) değerini 255 yapın:

resim1

Son olarak da “Source Image” değişkeninin değerini None yapın çünkü arkaplan için referans bir resme ihtiyacımız yok, arkaplan sadece siyah renkten oluşacak.

Şimdi GameObject-UI-Text ile sahnede bir yazı oluşturun ve yazıya “Durum” ismini verin. Yazının rengini istediğiniz gibi değiştirin, ben beyaz yaptım. Ayrıca dilerseniz Add Component-UI-Effects-Outline yolunu izleyerek yazıya bir dış hat da ekleyebilirsiniz. Ben mavi bir dış hat verdim:

resim2

Text component‘inin “Text” değişenine değer olarak “Yükleniyor %0” verin. Ardından anchor’lar ve font size ile oynayarak yazının ekranda kapladığı alanı genişletin zira şu anda yazı çok ufak. Benim yaptığım ayarlamalar şöyle:

resim3

Arayüzümüz neredeyse hazır. Hierarchy‘den Canvas objesini seçin ve Canvas component‘indeki “Sort Order“ı 100 yapın. Bir sahnede birden çok canvas varsa en üste sort order’ı büyük olan canvas çizilir. Biz loading ekranının daima en üstte olmasını istiyoruz.

Bence arayüzümüz tamamlandı. Şimdi bu arayüzü bir prefab‘a çevirelim. Neden diye sorabilirsiniz. Çünkü bu loading ekranından her sahneye bir tane koymakla uğraşmak istemiyorum. Ne zaman ki loading ekranını kullanacağız, o vakit bu prefab’ı kod vasıtasıyla Instantiate edeceğiz. Instantiate edebilmek için ise objeyi prefab yapmalıyız. O halde Project panelinde “Resources” adında yeni bir klasör oluşturun (sebebini kodu yazarken açıklayacağım) ve Canvas objesini Resources klasörünün içine sürükle-bırak yaparak bir prefab’a çevirin. Prefab’ın ismini “LoadingEkrani” yapın:

resim4

Kod yazmaya başlayabiliriz. “LoadingEkrani” adında yeni bir C# scripti oluşturun ve içini şöyle değiştirin:

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
 
public class LoadingEkrani : MonoBehaviour
{
    // LoadingEkrani scriptine kolayca erişebilmek için static değişken
    private static LoadingEkrani instance = null;
 
    public Image panel;
    public Text yazi;
 
    private Color panelRenk;
    private Color yaziRenk;
 
    // Değeri arttıkça yumuşak geçiş animasyonu hızlanır
    public float animasyonHizi = 1.25f;
 
    // Level yükleme işlemi
    private AsyncOperation loadingIslemi;
 
    // Yeni bir level yükle
    public static void LevelYukle( string levelAdi )
    {
        // Eğer sahnede bir LoadingEkrani yoksa yeni bir tane oluştur
        if( instance == null )
        {
            instance = Instantiate( Resources.Load<LoadingEkrani>( "LoadingEkrani" ) ); // Resources klasöründen LoadingEkrani prefab'ını yükle
            DontDestroyOnLoad( instance.gameObject ); // Loading ekranı sahneler arası geçişte kaybolmasın
        }
 
        // Loading ekranını aktifleştir
        instance.gameObject.SetActive( true );
 
        // Yeni leveli yüklemeye başla
        instance.loadingIslemi = SceneManager.LoadSceneAsync( levelAdi );
 
        // Yeni levelin yüklenmesi tammalansa bile hemen yeni levela geçiş yapma
        instance.loadingIslemi.allowSceneActivation = false;
    }
 
    void Awake()
    {
        // Panel ve yazının renklerini değişkenlerde depola
        panelRenk = panel.color;
        yaziRenk = yazi.color;
 
        // En başta loading ekranını görünmez yap
        panelRenk.a = 0;
        yaziRenk.a = 0;
 
        // Görünmez yaptığımız renkleri panel ve yazıya değer olarak ver
        panel.color = panelRenk;
        yazi.color = yaziRenk;
    }
 
    void Update()
    {
        // Yükleme yüzdesini güncelle
        yazi.text = "Yükleniyor %" + (int) ( loadingIslemi.progress * 100 );
 
        // Yükleme tamamlandıysa
        if( loadingIslemi.isDone )
        {
            // Loading ekranını yumuşak bir şekilde kaybet (fade out)
            panelRenk.a -= animasyonHizi * Time.unscaledDeltaTime;
            yaziRenk.a -= animasyonHizi * Time.unscaledDeltaTime;
 
            panel.color = panelRenk;
            yazi.color = yaziRenk;
 
            // Loading ekranı tamamen kaybolduysa objeyi deaktif et
            if( panelRenk.a <= 0 ) // >
            {
                gameObject.SetActive( false );
            }
        }
        else // Yükleme işlemi bitmediyse
        {
            // Loading ekranını yumuşak bir şekilde görünür yap (fade in) 
            panelRenk.a += animasyonHizi * Time.unscaledDeltaTime;
            yaziRenk.a += animasyonHizi * Time.unscaledDeltaTime;
 
            panel.color = panelRenk;
            yazi.color = yaziRenk;
 
            // Loading ekranı tamamen görünür hale geldiyse 
            if( panelRenk.a >= 1 )
            {
                // Artık yükleme işlemi bitince yeni levela geçebilelim
                loadingIslemi.allowSceneActivation = true;
 
                // Renklerin görünürlüğü 1'in üzerine çıkmamalı
                panelRenk.a = 1;
                yaziRenk.a = 1;
            }
        }
    }
}

Bu kod biraz uzun durabilir ama aslında anlaması zor olmayan bir kod. Kısaca kodu açıklayacak olursam:

static bir değişken (instance) vasıtasıyla singleton pattern‘den faydalanıyoruz. Loading ekranımızdan oyunda aynı anda sadece bir tane olmalı ve static değişken sayesinde bunu sağlayabiliyoruz.

animasyonHizi adında bir değişkenimiz var. Bu değişkenin değeri 1 olursa loading ekranının belirme süresi 1 saniye olurken değişkenin değeri 2 olursa loading ekranının belirme süresi 0.5 saniye olur. Yani bu değişkenin değeri arttıkça loading ekranının belirme süresi kısalıyor.

loadingIslemi dediğimiz değişken ise, oyun sırasında arkaplanda başka bir level yüklememize olanak sağlıyor. Böylece oyuncu mevcut leveli görürken aslında arkaplanda yeni bir level yüklenmekte oluyor.

Scriptimizde LevelYukle adında static bir fonksiyon bulunmakta. Yeni bir level yüklerken kullanmanız gereken fonksiyon bu. Yani artık SceneManager.LoadScene(“level1”) yazmak yerine LoadingEkrani.LevelYukle(“level1”) yazmalısınız. Bu fonksiyonun içerisinde öncelikle singleton’u sağlıyoruz. Yani eğer sahnemizde bir loading ekranı objesi yoksa yeni bir tane oluşturuyoruz (Instantiate) ve DontDestroyOnLoad komutu ile sahneler arası geçişlerde bu objenin yok olmasının önüne geçiyoruz. Instantiate fonksiyonu içerisinde referans objeyi çekerken Resources.Load fonksiyonunu kullanıyoruz. İşte Resources klasörü burada devreye giriyor. Scriptlerinizden Project klasöründeki bir asset’e erişmek istiyorsanız, yapmanız gereken şey o asset’i Resources klasörü içine atmak. Artık Resources.Load fonksiyonu ile o asset’e erişebilirsiniz. Fonksiyondaki “<LoadingEkrani>” kısmını yadırgayabilirsiniz, generic fonksiyonlar bu şekilde çağrılır. Bu kısmın yaptığı şey, yüklediğimiz asset’in türünü belirlemektir.

Singleton’u hallettikten sonra SceneManager.LoadSceneAsync fonksiyonu vasıtasıyla, yeni levelin arkaplanda yüklenmesi işlemini resmî olarak başlatıyoruz. loadingIslemi değişkeninin allowSceneActivation değişkeniniyse false yapıyoruz. Eğer bu değişken false olursa, arkaplandaki levelin yüklenmesi işlemi bitse dahi o levele anında geçiş yapılmaz. Bunu yapıyoruz çünkü diğer levele geçmeden önce loading ekranımızın ekranda tamamen belirmesini istiyoruz. Böylece yeni levela geçince sahne gözümüz önünde küt diye değişmeyecek çünkü loading ekranımız sahneyi kaplayarak bizim bu geçişi görmemizi engelleyecek.

Awake fonksiyonu, obje oluşturulduğu anda tek seferlik çalıştırılır. Bu fonksiyonda tek yaptığımız şey loading ekranını görünmez kılmak. Zira en başta loading ekranı yumuşak bir şekilde yoktan belirecek.

Update fonksiyonu ise yumuşak belirme/kaybolma animasyonlarını kodladığımız yer. Öncelikle sahnenin % kaçının yüklendiğini Durum yazısına iletiyoruz. Sahnenin % kaçının yüklendiğini loadingIslemi‘nin progress değişkeni depolamakta. Bu değişken 0 ile 1 arasında bir değer alır (örneğin 0.76). Bu değeri 100 ile çarparak %’lik hale dönüştürüyoruz (%76 gibi).

loadingIslemi‘nin isDone adında bir değişkeni bulunmakta. Diğer levelin yüklenmesi tamamlandıysa ve loadingIslemi’nin allowSceneActivation’ı true ise bu değişken true değeri alır, yoksa false değeri alır. Eğer diğer levela geçiş yaptıysak (isDone true ise), loading ekranının ekrandan yumuşak bir şekilde kaybolmasını ayarlıyoruz. Bunun için Panel’in ve Text’in color değişkenlerinin alpha‘sını (görünürlük) azaltıyoruz. Eğer loading ekranı tamamen kaybolduysa, objeyi deaktif ederek artık Update’in çalışmasını engelliyoruz.

Eğer isDone false ise, yani diğer level hâlâ yükleniyor ise, loading ekranını yumuşak bir şekilde görünür yapıyoruz. Loading ekranı tamamen görünür hale geldiğinde ise allowSceneActivation‘ı true yapıyoruz, böylece yeni levelin yüklenmesi tamamlandığında artık yeni levela güvenli bir şekilde geçiş yapabiliyoruz.

Basit anlamda scriptimiz böyle. Belki çok fazla yeni fonksiyon ve değişken görmüş ve biraz afallamış olabilirsiniz. Ama eminim ki takıldığınız noktaları Google amcaya sorarak çözebilirsiniz. Şimdi isterseniz scripti loading ekranına verelim. Bunun için scripti sürükleyerek sahnemizdeki LoadingEkrani objesine (Canvas) verin. Ardından Panel ve Yazi public değişkenlerine Inspector‘dan değerlerini verin:

resim5

Son olarak da bu değişiklikleri prefab’a uygulamak için Inspector‘un tepesindeki Apply butonuna tıklayın (Unity’nin son sürümlerinde Apply butonu yerine Overrides-Apply All butonu vardır). İşte bu kadar! Artık sahnedeki canvas objesini rahatça silebilirsiniz. Geriye sadece sistemi test etmek kalıyor. Bunun içinse tek yapmanız gereken, kendi kodlarınızdaki SceneManager.LoadScene fonksiyonlarını LoadingEkrani.LevelYukle fonksiyonu ile değiştirmek.

Umarım faydalı olur, sonraki derslerde görüşmek dileğiyle!

yorum
  1. Barış dedi ki:

    Yani kodu herhangi bir objeye mi atayım?
    Instantiate Olması için kodun çalışması gerek kodun çalışması içinse bir objeye atıyorum hocam.

    • yasirkula dedi ki:

      İstediğiniz herhangi bir script’ten LoadingEkrani.LevelYukle fonksiyonunu çağırabilirsiniz, evet.

      • Barış dedi ki:

        Hocam kusuruma bakmayın biraz çok yazdım çok özür ama levelAdi mevzusunu anlamadım her şeyi anladım LoadingEkrani.LevelYukle falanı anladım ama levelAdi na acaba ne yazacağım veya levelAdi diye mi kalsın script te?

      • yasirkula dedi ki:

        LoadingEkrani isimli script’i düzenliyorsanız düzenlemeyin çünkü LoadingEkrani.LevelYukle(“Scene’in İsmi”) fonksiyonunu o script’te değil kendi script’lerinizde çağıracaksınız.

  2. Barış dedi ki:

    NullReferenceException: Object reference not set to an instance of an object
    LoadingEkrani.Update () (at Assets/Codes/LoadingEkrani.cs:60)
    Hata var hocam. https://www.hizliresim.com/ncb9sxz sahne böyle.

  3. Barış dedi ki:

    Örnek veriyorum 5 bölümlük oyunumuz var 5 ayrı scenede ve bölüm seçmek için ayrı bir scene var toplam 6 scene olsun. Bu canvas prefab ını hangi sahneye atmam gerek?

    Ayrıca loading işlemi bitince sahnemizde zaman hemen başlar mı çünkü;
    Daha denemedim ama oyunumda ara sahne var ara sahnenin bir kısmı gözükmeme gibi bir durum olur mu hocam?

    • yasirkula dedi ki:

      Prefab ihtiyaç halinde otomatik olarak Instantiate oluyor o yüzden bir sahneye koymanıza gerek yok. Loading işlemi bitip diğer sahne yüklenince zaman hemen başlar. Bu sürenin ilk yaklaşık 1 saniyesinde loading ekranı ekrandan yumuşak bir şekilde kaybolacağı için, o esnada oyuncunun görmesi gereken kritik bir şey varsa onu oyuncu maalesef göremez. Bunu çözmek için “loadingIslemi.allowSceneActivation = true;” satırından önce Time.timeScale’i 0 yapıp “gameObject.SetActive( false );” satırından sonra da Time.timeScale’i 1 yapmayı deneyebilirsiniz.

  4. Ahmet dedi ki:

    Hocam benim genel olarak şöyle bir sıkıntım var. Diğer izlediğim tüm videolarda da aynı sıkıntıyla karşılaştım. Görünüşe göre LoadSceneAsync methodu sahneyi arka planda yüklemeye tam olarak yardımcı olmuyor. Telefonda oyunumu test ettiğimde Unity logosunu geçtikten sonra oyunumun açılma süresi 5 saniye sürüyor. Telefondan telefona değişiyor. Hayal ettiğim şey şu aslında. 5 saniye boyunca yükleniyor ibaresinin ilerlemesi gerekiyor. Ama gel gelelim gerçekte bu olmuyor. Oyunun ilk saniyesinde yüzde değerini gösteriyor. Bir de 5. saniyede yüzde değerini gösteriyor. O aradaki 4 buçuk saniye boyunca hiç bir şey yapmıyor. Mevcut sahnedeyken, arka planda 2. sahneyi yükle dediğimiz anda hali hazırdaki sahnemizde donuyor.Tam o esnada Ne animasyon oynuyor. Ne başka bir şey.Yaprak kıpırdamıyor.

    Bu yukarıda anlattığınız loading screen boyutu büyük oyunlar için mi geçerlidir. Yoksa benim unity sürümünde mi bir hata var. Biraz fazla uzattım kusura bakmayın. Eminim aynı sorunla karşılaşan başka arkadaşlar da vardır.

    • yasirkula dedi ki:

      Progress niye sizde bir anda zıplıyor onu bilmiyorum. Takılma sorununun sebebi için şu cevap bana mantıklı geldi. Özetle, sahnedeki objelerin Awake ve Start fonksiyonları ana thread’de çalıştığı için, o fonksiyonlar oyunu kastırıyordur. Buna güzel bir çözüm benim aklıma gelmiyor ama belki bilmediğim bir çözüm vardır.

  5. Ahmet dedi ki:

    Scripti canvasa sürükledim. Canvası silmek yerine false yaptım. Ayrıca yine ilk sahneye bir tane daha canvas oluşturdum. İçine de bir tane buton ekledim. Butona tıklayınca LevelYukle (string) metodunu tetikledim. İşlem tamamdır.

  6. Ahmet dedi ki:

    Hocam iki sahnem var. Birinci sahnenin adı >> yükleniyor. İkinci sahnenin adı >> LevelAdi. Yukarıdaki anlatılanların aynısını yaptım. 60. satırda şu hatayı gösteriyor. NullReferenceException: Object reference not set to an instance of an object
    LoadingEkrani.Update () (at Assets/LoadingEkrani.cs:60). Scripti tam olarak nereye sürükleyeceğiz. Sonrasında bir şeyler daha anlatmışsınız. Son bir kaç cümlenizi tam olarak anlayamadım hocam.

    • yasirkula dedi ki:

      En son resimde, component’i nereye verdiğimi ve değişkenlerine ne değerler verdiğimi görebilirsiniz. Eğer LoadingEkrani canvas’ını sahnenizde bıraktıysanız, onu silin. Kod bu canvas’ın prefabını oyun esnasında Instantiate edecek.

  7. Burak dedi ki:

    Unity 4 desteği yok. Unity 4 SceneManagement desteklemediği için çalışmıyor. Unity 4 için de yardımcı olabilir misiniz ?

    • yasirkula dedi ki:

      SceneManager.LoadSceneAsync’i Application.LoadLevelAsync ile değiştirebilirsiniz ama anladığım kadarıyla bu fonksiyon Unity Pro gerektiriyor.

  8. Bayram dedi ki:

    hocam buttonların child olarak text mesh proları var.Level butonlarınada 1 levelden 10.levele kadar olan butonları attım ama bu hataları alıyorum

    NullReferenceException: Object reference not set to an instance of an object
    level_menu_button.LevelButonlariniGuncelle () (at Assets/kodlar/level_menu_button.cs:47)
    level_menu_button.SagaGit () (at Assets/kodlar/level_menu_button.cs:40)

  9. Bayram dedi ki:

    Hocam diyelim oyun 50 levelli olsun ve her sayfada 10 level görünsün ve sağa,sola butonlar yerleştirip bu butonlara basınca leveller menüsünde gezebilsin şuradaki butonlar gibi
    https://i.hizliresim.com/JVaYWE.jpg
    Bunu nasıl yapabilirim?

    • yasirkula dedi ki:

      Şuna benzer bir kod yazabilirsiniz:

      public Button[] levelButonlari;
      private int ilkLevel = 1, sonLevel = 10;
      
      void Start()
      {
      	LevelButonlariniGuncelle();
      	
      	for( int i = 0; i < 9; i++ )
      	{
      		int j = i;
      		levelButonlari[i].onClick.AddListener( () => LevelButonunaBasildi( j ) );
      	}
      }
      
      public void SolaGit()
      {
      	if( ilkLevel > 10 )
      	{
      		ilkLevel -= 10;
      		sonLevel -= 10;
      		
      		LevelButonlariniGuncelle();
      	}
      }
      
      public void SagaGit()
      {
      	if( sonLevel <= 40 )
      	{
      		ilkLevel += 10;
      		sonLevel += 10;
      		
      		LevelButonlariniGuncelle();
      	}
      }
      
      private void LevelButonlariniGuncelle()
      {
      	for( int i = 0; i < 9; i++ )
      		levelButonlari[i].GetComponentInChildren<Text>().text = ( ilkLevel + i ).ToString();
      }
      
      private void LevelButonunaBasildi( int index )
      {
      	int levelIndex = ilkLevel + index;
      	
      	// levelIndex 1'den 50'ye kadar bir değere sahip, burada o index'teki level'i başlat
      }
      

      Ardından sol ok butonunun On Click event’ine SolaGit fonksiyonunu, sağ ok butonunun On Click event’ine de SagaGit fonksiyonunu değer olarak vermeli ve script’in “Level Butonlari” değişkenine de 10 tane level butonunu değer olarak eklemelisiniz (bu butonların sıralaması baştan sona doğru olmalı).

      • Bayram dedi ki:

        NullReferenceException: Object reference not set to an instance of an object
        level_menu_button.LevelButonlariniGuncelle () (at Assets/kodlar/level_menu_button.cs:47)
        level_menu_button.SagaGit () (at Assets/kodlar/level_menu_button.cs:40)

        Hocam bu hataları alıyorum.

      • yasirkula dedi ki:

        Ya levelButonlari’na Inspector’dan değer vermemişsinizdir, ya da butonlarınızın içinde bir Text objesi yoktur (Text, butonun child objesi ise de düzgün çalışır).

Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.