Unity, Profiler'ı Kullanarak Oyununuzu Optimize Edin

Performans herhangi bir oyunun önemli bir unsurudur ve oyun ne kadar iyi olursa olsun, kullanıcının bilgisayarında kötü çalışırsa, o kadar eğlenceli hissetmemesi şaşırtıcı değildir.

Herkesin ileri teknoloji bir bilgisayarı veya cihazı olmadığından (eğer mobil cihazı hedefliyorsanız), tüm geliştirme süreci boyunca performansı akılda tutmak önemlidir.

Oyunun yavaş çalışmasının birden fazla nedeni vardır:

  • Oluşturma (Çok fazla yüksek poli ağ, karmaşık gölgelendirici veya görüntü efekti)
  • Ses (Çoğunlukla yanlış ses içe aktarma ayarlarından kaynaklanır)
  • Optimize Edilmemiş Kod (Performans gerektiren işlevleri yanlış yerlerde içeren komut dosyaları)

Bu derste, Unity Profiler'ın yardımıyla kodunuzu nasıl optimize edebileceğinizi göstereceğim.

Profil oluşturucu

Geçmişte Unity performansında hata ayıklama sıkıcı bir işti, ancak o zamandan beri Profiler adı verilen yeni bir özellik eklendi.

Profiler, Unity'deki bir araçtır ve bellek tüketimini izleyerek oyununuzdaki darboğazları hızlı bir şekilde belirlemenizi sağlar, bu da optimizasyon sürecini büyük ölçüde basitleştirir.

Unity Profiler Penceresi

Kötü performans

Kötü performans her an ortaya çıkabilir: Diyelim ki düşman örneği üzerinde çalışıyorsunuz ve onu sahneye yerleştirdiğinizde sorunsuz çalışıyor, ancak daha fazla düşman ortaya çıkardıkça fps (saniyede kare) fark edebilirsiniz. ) düşmeye başlar.

Aşağıdaki örneği kontrol edin:

Sahnede, Küpü bir yandan diğer yana hareket ettiren ve nesne adını görüntüleyen, üzerine bir komut dosyası eklenmiş bir Küpüm var:

SC_ShowName.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SC_ShowName : MonoBehaviour
{
    bool moveLeft = true;
    float movedDistance = 0;

    // Start is called before the first frame update
    void Start()
    {
        moveLeft = Random.Range(0, 10) > 5;
    }

    // Update is called once per frame
    void Update()
    {
        //Move left and right in ping-pong fashion
        if (moveLeft)
        {
            if(movedDistance > -2)
            {
                movedDistance -= Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x -= Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = false;
            }
        }
        else
        {
            if (movedDistance < 2)
            {
                movedDistance += Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x += Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = true;
            }
        }
    }

    void OnGUI()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }
}

İstatistiklere baktığımızda oyunun 800+ fps hızında çalıştığını görebiliyoruz, dolayısıyla performans üzerinde neredeyse hiç etkisi yok.

Ama Küpü 100 kere kopyaladığımızda bakalım ne olacak:

Fps 700 puandan fazla düştü!

NOT: Tüm testler Vsync devre dışıyken yapıldı

Genel olarak oyunda takılmalar, donmalar görülmeye başladığında veya fps 120'nin altına düştüğünde optimizasyona başlamak iyi bir fikirdir.

Profiler Nasıl Kullanılır?

Profiler'ı kullanmaya başlamak için ihtiyacınız olacak:

  • Play tuşuna basarak oyununuzu başlatın
  • Profiler'ı Pencere -> Analiz -> Profiler'a giderek açın (veya Ctrl + 7 tuşlarına basın)

  • Şuna benzeyen yeni bir pencere görünecektir:

Unity 3D Profil Oluşturucu Penceresi

  • İlk başta korkutucu görünebilir (özellikle tüm bu grafikler vb.), ancak bakacağımız kısım bu değil.
  • Zaman Çizelgesi sekmesine tıklayın ve Hiyerarşi olarak değiştirin:

  • 3 bölüm göreceksiniz (EditorLoop, PlayerLoop ve Profiler.CollectEditorStats):

  • Hesaplama gücünün harcandığı tüm bölümleri görmek için PlayerLoop'u genişletin (NOT: PlayerLoop değerleri güncellenmiyorsa Profiler penceresinin üst kısmındaki "Clear" düğmesine tıklayın).

En iyi sonuçları elde etmek için oyun karakterinizi oyunun en çok geciktiği duruma (veya yere) yönlendirin ve birkaç saniye bekleyin.

  • Biraz bekledikten sonra oyunu durdurun ve PlayerLoop listesini gözlemleyin.

Çöp Toplama Tahsisi anlamına gelen GC Alloc değerine bakmanız gerekiyor. Bu, bileşeni tarafından ayrılmış olan ancak artık ihtiyaç duyulmayan ve Çöp Toplama tarafından serbest bırakılmayı bekleyen bir bellek türüdür. İdeal olarak, kodun herhangi bir çöp üretmemesi (veya mümkün olduğunca 0'a yakın olması) gerekir.

Time ms da önemli bir değerdir; kodun çalışmasının milisaniye cinsinden ne kadar sürdüğünü gösterir; dolayısıyla ideal olarak bu değeri de azaltmayı hedeflemelisiniz (değerleri önbelleğe alarak, her Güncellemede performans gerektiren işlevleri çağırmaktan kaçınarak vb.).).

Sorunlu parçaları daha hızlı bulmak için, değerleri yüksekten düşüğe doğru sıralamak üzere GC Alloc sütununa tıklayın.)

  • CPU Kullanımı tablosunda o kareye atlamak için herhangi bir yere tıklayın. Özellikle fps'nin en düşük olduğu zirvelere bakmamız gerekiyor:

Unity CPU Kullanım Tablosu

İşte Profiler'ın ortaya çıkardığı şey:

GUI.Repaint 45,4 KB ayırıyor ki bu oldukça fazla, genişleterek daha fazla bilgi ortaya çıktı:

  • Optimizasyona başlayabileceğimizi bilerek, ayırmaların çoğunun SC_ShowName betiğindeki GUIUtility.BeginGUI() ve OnGUI() yönteminden geldiğini gösterir.

GUIUtility.BeginGUI() boş bir OnGUI() yöntemini temsil eder (Evet, boş OnGUI() yöntemi bile oldukça fazla bellek ayırır).

Tanımadığınız adları bulmak için Google'ı (veya başka bir arama motorunu) kullanın.

Optimize edilmesi gereken OnGUI() kısmı:

    void OnGUI()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }

Optimizasyon

Optimize etmeye başlayalım.

Her SC_ShowName betiği kendi OnGUI() yöntemini çağırır; bu da 100 örneğimiz olduğu düşünülürse iyi bir şey değildir. Peki bu konuda ne yapılabilir? Cevap şudur: Her Küp için GUI yöntemini çağıran OnGUI() yöntemine sahip tek bir komut dosyasına sahip olmak.

  • İlk olarak, SC_ShowName betiğindeki varsayılan OnGUI() işlevini, başka bir betikten çağrılacak olan public void GUIMethod() ile değiştirdim:
    public void GUIMethod()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }
  • Sonra yeni bir komut dosyası oluşturdum ve buna SC_GUIMethod adını verdim:

SC_GUIMethod.cs

using UnityEngine;

public class SC_GUIMethod : MonoBehaviour
{
    SC_ShowName[] instances; //All instances where GUI method will be called

    void Start()
    {
        //Find all instances
        instances = FindObjectsOfType<SC_ShowName>();
    }

    void OnGUI()
    {
        for(int i = 0; i < instances.Length; i++)
        {
            instances[i].GUIMethod();
        }
    }
}

SC_GUIMethod sahnedeki rastgele bir nesneye eklenecek ve tüm GUI yöntemlerini çağıracaktır.

  • 100 ayrı OnGUI() yönteminden yalnızca bir taneye dönüştük, haydi oynat tuşuna basalım ve sonucu görelim:

  • GUIUtility.BeginGUI() artık 36,7 KB yerine yalnızca 368B ayırıyor, bu büyük bir azalma!

Bununla birlikte, OnGUI() yöntemi hala bellek ayırıyor, ancak bunun yalnızca SC_ShowName betiğinden GUIMethod() öğesini çağırdığını bildiğimizden, doğrudan bu yöntemin hatalarını ayıklamaya gidiyoruz.

Ancak Profiler yalnızca genel bilgileri gösterir; yöntemin içinde tam olarak ne olduğunu nasıl görebiliriz?

Yöntemin içinde hata ayıklamak için Unity, Profiler.BeginSample adında kullanışlı bir API'ye sahiptir.

Profiler.BeginSample, betiğin belirli bir bölümünü yakalamanıza olanak tanıyarak tamamlanmasının ne kadar sürdüğünü ve ne kadar bellek ayrıldığını gösterir.

  • Profiler sınıfını kodda kullanmadan önce, betiğin başlangıcındaki UnityEngine.Profiling ad alanını içe aktarmamız gerekir:
using UnityEngine.Profiling;
  • Profiler Örneği, yakalamanın başlangıcına Profiler.BeginSample("SOME_NAME"); eklenerek ve yakalamanın sonuna Profiler.EndSample(); eklenerek yakalanır, örneğin Bu:
        Profiler.BeginSample("SOME_CODE");
        //...your code goes here
        Profiler.EndSample();

GUIMethod() işlevinin hangi bölümünün bellek ayırmalarına neden olduğunu bilmediğim için, her satırı Profiler.BeginSample ve Profiler.EndSample içine aldım (Ancak yönteminizde çok fazla satır varsa, kesinlikle eklemenize gerek yoktur) her satırı eşit parçalara bölün ve oradan çalışın).

Profiler Samples'ın uygulandığı son yöntem aşağıda verilmiştir:

    public void GUIMethod()
    {
        //Show object name on screen
        Profiler.BeginSample("sc_show_name part 1");
        Camera mainCamera = Camera.main;
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 2");
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 3");
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
        Profiler.EndSample();
    }
  • Şimdi Oynat'a basıyorum ve Profiler'da ne gösterildiğini görüyorum:
  • Kolaylık sağlamak için Profiler'da "sc_show_"'yi aradım çünkü tüm örnekler bu adla başlıyor.

  • İlginç... Sc_show_names bölüm 3'te kodun bu bölümüne karşılık gelen çok fazla bellek ayrılıyor:
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);

Biraz Google'da araştırdıktan sonra Object'in adını almanın oldukça fazla hafıza ayırdığını keşfettim. Çözüm, bir Nesnenin adını void Start() içindeki bir dize değişkenine atamaktır, bu şekilde yalnızca bir kez çağrılacaktır.

İşte optimize edilmiş kod:

SC_ShowName.cs

using UnityEngine;
using UnityEngine.Profiling;

public class SC_ShowName : MonoBehaviour
{
    bool moveLeft = true;
    float movedDistance = 0;

    string objectName = "";

    // Start is called before the first frame update
    void Start()
    {
        moveLeft = Random.Range(0, 10) > 5;
        objectName = gameObject.name; //Store Object name to a variable
    }

    // Update is called once per frame
    void Update()
    {
        //Move left and right in ping-pong fashion
        if (moveLeft)
        {
            if(movedDistance > -2)
            {
                movedDistance -= Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x -= Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = false;
            }
        }
        else
        {
            if (movedDistance < 2)
            {
                movedDistance += Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x += Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = true;
            }
        }
    }

    public void GUIMethod()
    {
        //Show object name on screen
        Profiler.BeginSample("sc_show_name part 1");
        Camera mainCamera = Camera.main;
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 2");
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 3");
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), objectName);
        Profiler.EndSample();
    }
}
  • Profiler'ın ne gösterdiğine bakalım:

Tüm örnekler 0B'yi ayırıyor, dolayısıyla daha fazla bellek ayrılmıyor.

Önerilen Makaleler
Unity için Optimizasyon İpuçları
Unity'de Güncelleme Nasıl Kullanılır?
Birlik için Billboard Jeneratörü
Unity'de Mobil Oyunun Performansını Artırma
En İyi Performans için Unity Ses Klibi İçe Aktarma Ayarları
Unity'de Nasıl Daha İyi Bir Programcı Olunur?
Unity'de FNAF'tan Esinlenen Bir Oyun Nasıl Yapılır