GAME, SOFTWARE, NETWORKING AND MORE...

Contact us for cooperation

 

Unity - UI Optimizasyonu

Merhabalar. Bu yazıda geliştirdiğimiz oyunların büyük bir parçası olan UI’ların optimizasyonuna değineceğiz. Yabancı bir kaynağı Türkçeye tercüme ettiğim bu yazıda sıkça dirty kelimesinden bahsediliyor. Kelimeyi olduğu gibi Türkçeye çevirince kirli anlamı çıkıyor. Kirli kelimesinin bu aşamada çok yetersiz ve anlamsız olduğu düşündüğüm için kelimeyi olduğu gibi, tercüme etmeyip yazıda kullandım. Ama dirty’nin yazılımdaki tanımına kısaca; sisteme/yapıya/veri tabanına/diske senkronize edilmemiş içeriği değiştirilmiş nesne diyebiliriz.

 

Faydalandığım kaynak: https://tappable.co.uk/talking-dirty-unity-ui-optimisation/
 

---------------------------

 

Oyununu geliştirdin ve her şey bitti. Ama doğru gitmeyen bir şeyler var. Düşük FPS, yüksek CPU kullanımı, aşırı pil tüketimi vs. gibi bir takım optimizasyon problemlerinin olduğunu fark ediyorsun. Ve ardından “Oyunu optimize etmem gerek!” diyorsun…

Bugün; Unity’de performans kaybına yol açan az bilinen bazı yolları ve bunların nasıl düzeltileceği hakkında yazacağım.

UI sistemine derinlemesine dalmadan önce şunu bilin; oyununuzu her optimize ettiğinizde yapmanız gereken ilk şey her zaman sorunu bildiğinizden emin olmanızdır. Unity’nin Profiler’ı bu konuda size yardımcı olmakta mükemmel bir araçtır. Profiler sayesinde; oyunun nerelerde yavaşladığını, hangi senaryolarının çalıştığını, oyun fiziğinin nerede nasıl hesaplandığını, render’lanan UI’lar  ve diğer her şey hakkında bilgi sahibi olabilirsiniz.

“Ama bir dakika…Demek istediğim oyunun ana menüsündeyim ve hiç bir şey yapmadan oturuyorum, ona rağmen CPU deliriyor!”

Pekâlâ dostum, korkarım oyununda bir problem olabilir…

 

Dirty hakkında konuşalım

Aslında önce batching hakkında biraz konuşalım, batching nedir ve neden bunla ilgilenmeliyiz?

Batching, Unity’de oluşturduğunuz Canvas’lara dayanarak görsel sonuçlar üretme sürecidir. Canvas içinde oluşturduğunuz Image, Text vb. tüm UI component’lerinin mesh’leri birleştirilir ve ekranda işlemek için unity’nin grafik hattına gönderilir(Şunu asla unutmayın; UI componentlerinin canvas içinde oluşturduğu görsel olan her şey aslında mesh’tir. Örneğin siz oyun esnasında bir InputField’ı yani bir yazı kutusunu düzenlemeye çalıştığınızda, eklediğiniz ya da sildiğiniz her karakterde eski mesh silinip orda yeni bir mesh oluşturuluyor o text için). Bu işlem akıllıcadır, herhangi bir canvas değişikliği gerçekleşinceye kadar sonuçları önbelleğe alır. Yani canvas’ta ana menü’yü render’ladıktan sonra siz canvas’ta bir değişiklik yapmayıp, batching yapmayana dek sonuç hep aynı kareyi(frame’i) kullanır. Eğer bunun nasıl çalıştığı konusunda bilgi sahibi değilseniz, her frame’de batching işemi yapıp gereksizce UI’ların tekrar tekrar render’lanmasına sebep olabilirsiniz.

Canvas’ta batching işlemi ne zaman meydana gelir? –Ne zaman Canvas dirty olarak işaretlenirse(belirlenirse) o zaman.

Canvas’ın dirty olarak işaretlenmesine ne sebep olur? –Herşey!

Kelimenin tam anlamıyla, bir canvas’ta veya canvas’ın child’larında yapacağınız herhangi bir değişiklik(bu değişiklik bir text düzenlenmesinden tutun, bir image değişimine ordan da tutun bir RectTransform değişikliğine her şeyi kapsar), hierarchy’de en üste yani ilgili canvas’a ulaşıp o canvas’ı dirty olarak işaretleyeceğini söylüyorum. Canvas içindeki; bir Text değerini, RectTransform pozisyonunu, renkleri, scale’i, rotation’ı değiştirmek veya bir game object’i aktif etmek ana canvas’ı (Ana dediği parent canvas) dirty olarak işaretler. Şuan haklı olarak “Aman Allahım!” diye düşünebilirsiniz. Karmaşık bir UI’ın bir parçası olarak dönen süslü bir döndürücünüz varsa(Loading dönücüsü gibi), bu döndürücü bütün canvas’ınızın her frame’de yeniden render’lanmasına sebep oluyor olabilir. PC’de bu belki pek problem yaratmayabilir ama mobil’de cihazlarda pil’i etkiler.

 

Muhtemel Çözümler

Neyse ki bunu düzletmenin veya en aza indirmenin yolları var.

Bir UI’da yaptığınız değişikliklerin, parent canvas’ı dirty olarak işaretlediğini unutmayın. Bunun çözümü iç içe canvas’lar kullanmaktır. Ama gidip sahneneizdeki her game object’e canvas eklemeye çalışmayın çünkü bunu yaparsanız batching işleminin sürecini uzatırsınız. Bunu kullanmanın en iyi yolu canvas’taki obejct’leri statik ve dinamik olarak sıralamaktır. Örneğin; kenarlıklardan, text’ten, dolgudan, arka plan görüntüsünden oluşan bir sağlık bar’ınız varsa, text’i ve dolgu image’ını(bar) diğer statik’lerden(kenarlıklar ve arka plan) ayırır ve buna bir canvas eklersiniz(oluşturursunuz). Bu şekilde dolgu image’ı(bar) ve sağlığı gösteren text ile her oynama yaptığınızda bütün UI’ın sadece küçük bir kısmını dirty olarak işaretleyecek ve tüm UI’ların bunun için yeniden render’lanmasını engellemiş olacaksınız.

CPU’dan tasarruf etmenin bir başka yolu da game object’i devre dışı bırakmak yerine Canvas componentini devre dışı bırakmaktır. Çünkü böyle yapmanız durumunda canvas ı tekrar aktif ettiğiniz zaman canvas yeniden oluşturulmaz(yani dirty olarak işaretlenmez), devre dışı olmadan önceki son bıraktığı sonuçları kullanır. Bununla birlikte; canvas altında çalışan çok sayıda MonoBehaviour’ünüz(script) varsa ve devre dışı olarak ayarlamazsanız, onları tek tek manuel olarak devre dışı bırakmanız gerekebilir. Bunu aklınızda bulundurun.

 

Sonuncusu ama bir o kadar önemlisi, milisaniyelerce performans sağlamak için

Envanter sistemi gibi, oyun esnasında kullanıcı tarafından sürekli doldurulan/karışılan karmaşık UI’lar üzerinde çalışıyorsanız, genellikle kendinizi yeni bir oyun nesnesi(game object) oluşturma ve o nesneyi yapılandırma sürecinde bulacaksınız(Burada yeni oyun nesnesinden kasıt, örneğin envanter tablosunda herhangi bir kutucuğa bir item’in gelmesi gibi düşünebilirsiniz, bu ve bu gibi örnekler). Bilmelisiniz ki aktif olmayan bir game object, parent canvas’ı dirty olarak işaretlemek için rahatsız etmeyecektir canvas’ı. Ama eğer aktifse; hierarchy ağacına mesajlar gönderir, her bir parent’ı canvas componentleri için kontrol eder ve her değişiklikle canvas’ı dirty olarak işaretler.

Yani kodunuzu bu şekilde yapılandırmayın;

MonoBehaviourInventoryClass uiElement = Instantiate(prefab,layout).GetComponent();
uiElement.gameObject.SetActive(true); //Obje aktifleşti ve canvas dirty olarak işaretlendi
uiElement.image.fillAmount = 0.2f; //UI elementinde bir değişiklik yapıldı, canvas tekrar dirty olarak işaretlendi
uiElement.header.text = “Sword”; //Tekrar başka bir UI elementinde bir değişiklik yapıldı ve canvas tekrar dirty olarak işaretlendi

Gördüğünüz gibi canvas’ı tam 3 defa dirty olarak işaretledik, yani 3 defa canvas yeniden render’landı. Ama kodunuzu bu şekilde yapılandırırsanız canvas’ı sadece bir defa dirty olarak işaretlemiş olursunuz;

MonoBehaviourInventoryClass uiElement = Instantiate(prefab,layout).GetComponent();
uiElement.image.fillAmount = 0.2f;
uiElement.header.text = “Sword”;
uiElement.gameObject.SetActive(true); //Obje daha yeni aktif olduğu için sadece burada canvas’ı dirty olarak işaretler

Umarım bu sizin için verimli bir okumaydı. Şimdi gidin ve şu kare hızlarını(fps) artırın!

*Bu aşırı tepki, dramatik amaçlar içindi