Unity'de kullanıcı arayüzü performansının nasıl optimize edileceğini, sayısız deney, pratik tavsiye ve bunu destekleyen performans testleri içeren bu ayrıntılı kılavuzu kullanarak öğrenin!
Merhaba! Ben Sergey Begichev, Pixonic'te (MY.GAMES) Müşteri Geliştiricisiyim. Bu yazıda, Unity3D'de UI optimizasyonunu ele alacağım. Bir doku kümesini işlemek basit görünse de, önemli performans sorunlarına yol açabilir. Örneğin, War Robots projemizde, optimize edilmemiş UI sürümleri toplam CPU yükünün %30'una kadarını oluşturuyordu — şaşırtıcı bir rakam!
Tipik olarak, bu sorun iki koşul altında ortaya çıkar: birincisi, çok sayıda dinamik nesne olduğunda ve ikincisi, tasarımcılar farklı çözünürlüklerde güvenilir ölçeklemeyi önceliklendiren düzenler oluşturduğunda. Bu koşullar altında küçük bir kullanıcı arayüzü bile fark edilir bir yük oluşturabilir. Bunun nasıl çalıştığını inceleyelim, yükün nedenlerini belirleyelim ve olası çözümleri tartışalım.
İlk önce gözden geçirelim
2. ve 3. maddeler sezgisel olarak açık olsa da, önerilerin geri kalanı pratikte hayal edilmesi sorunlu olabilir. Örneğin, "tuvallerinizi alt tuvallere ayırın" tavsiyesi kesinlikle değerlidir, ancak Unity bu bölünmenin ardındaki ilkeler hakkında net yönergeler sağlamaz. Kendi adıma, pratik açıdan, alt tuvalleri uygulamanın en mantıklı olduğu yeri bilmek istiyorum.
"Düzen gruplarından kaçınma" tavsiyesini göz önünde bulundurun. Bunlar yüksek bir kullanıcı arayüzü yüküne katkıda bulunabilse de, birçok büyük kullanıcı arayüzü birden fazla düzen grubuyla gelir ve her şeyi yeniden düzenlemek zaman alıcı olabilir. Dahası, düzen gruplarından kaçınan düzen tasarımcıları kendilerini görevlerine önemli ölçüde daha fazla zaman harcarken bulabilirler. Bu nedenle, bu tür gruplardan ne zaman kaçınılması gerektiğini, ne zaman faydalı olabileceklerini ve bunları ortadan kaldıramazsak hangi eylemleri gerçekleştirmemiz gerektiğini anlamak faydalı olacaktır.
Unity'nin önerilerindeki bu belirsizlik temel bir sorundur; bu önerilere hangi ilkeleri uygulamamız gerektiği çoğu zaman belirsizdir.
UI performansını optimize etmek için, Unity'nin UI'yi nasıl oluşturduğunu anlamak esastır. Bu aşamaları anlamak, Unity'de etkili UI optimizasyonu için çok önemlidir. Bu süreçte genel olarak üç temel aşamayı belirleyebiliriz:
Düzen . Başlangıçta, Unity tüm kullanıcı arayüzü öğelerini boyutlarına ve belirlenmiş konumlarına göre düzenler. Bu konumlar ekran kenarları ve diğer öğelerle ilişkili olarak hesaplanır ve bir bağımlılık zinciri oluşturur.
Toplu işleme . Ardından, Unity daha verimli işleme için ayrı öğeleri gruplara ayırır. Tek bir büyük öğeyi çizmek, birden fazla küçük öğeyi işlemekten her zaman daha verimlidir. (Toplu işleme hakkında daha derin bir inceleme için bkz.
İşleme . Son olarak, Unity toplanan grupları çizer. Grup sayısı ne kadar az olursa, işleme süreci o kadar hızlı olur.
Sürecin başka unsurları da olsa da, sorunların çoğunluğunu bu üç aşama oluşturuyor, şimdilik bunlara odaklanalım.
İdeal olarak, kullanıcı arayüzümüz statik kaldığında, yani hiçbir şey hareket etmediğinde veya değişmediğinde, düzeni bir kez oluşturabilir, tek bir büyük grup oluşturabilir ve onu verimli bir şekilde işleyebiliriz.
Ancak, tek bir öğenin bile konumunu değiştirirsek, konumunu yeniden hesaplamalı ve etkilenen grubu yeniden oluşturmalıyız. Diğer öğeler bu konuma bağlıysa, o zaman onların konumlarını da yeniden hesaplamamız gerekir ve bu da hiyerarşi boyunca kademeli bir etki yaratır. Ve ayarlanması gereken öğe sayısı arttıkça, gruplama yükü de artar.
Yani, bir düzendeki değişiklikler tüm kullanıcı arayüzünde bir dalgalanma etkisi yaratabilir ve bizim hedefimiz değişiklik sayısını en aza indirmektir. (Alternatif olarak, bir zincirleme reaksiyonu önlemek için değişiklikleri izole etmeyi hedefleyebiliriz.)
Pratik bir örnek olarak, bu sorun özellikle düzen grupları kullanıldığında belirginleşir. Bir düzen her yeniden oluşturulduğunda, her LayoutElement bir GetComponent işlemi gerçekleştirir ve bu oldukça kaynak yoğun olabilir.
Performans sonuçlarını karşılaştırmak için bir dizi örneği inceleyelim. (Tüm testler Google Pixel 1 cihazında Unity sürüm 2022.3.24f1 kullanılarak gerçekleştirildi.)
Bu testte, tek bir öğeye sahip bir düzen grubu oluşturacağız ve iki senaryoyu analiz edeceğiz: birinci senaryoda öğenin boyutunu değiştireceğiz ve ikinci senaryoda FillAmount özelliğini kullanacağız.
RectTransform değişiklikleri:
FlllAmount değişiklikleri:
İkinci örnekte, aynı şeyi yapmaya çalışacağız, ancak 8 öğeli bir düzen grubunda. Bu durumda, yine de yalnızca bir öğeyi değiştireceğiz.
RectTransform değişiklikleri:
FlllAmount değişiklikleri:
Önceki örnekte, RectTransform'daki değişiklikler düzende 0,2 ms'lik bir yüke neden olduysa, bu sefer yük 0,7 ms'ye çıkar. Benzer şekilde, toplu güncellemelerden kaynaklanan yük 0,65 ms'den 1,10 ms'ye çıkar.
Hala sadece bir öğeyi değiştiriyor olsak da, düzenin artan boyutu yeniden inşa sırasında yükü önemli ölçüde etkiliyor.
Buna karşılık, bir öğenin FillAmount'unu ayarladığımızda, daha fazla öğe sayısı olsa bile yükte bir artış gözlemlemiyoruz. Bunun nedeni, FillAmount'u değiştirmenin bir düzen yeniden oluşturmayı tetiklememesi ve bunun sonucunda toplu güncelleme yükünde yalnızca hafif bir artış olmasıdır.
Açıkçası, bu senaryoda FillAmount kullanmak daha verimli bir seçimdir. Ancak, bir öğenin ölçeğini veya konumunu değiştirdiğimizde durum daha karmaşık hale gelir. Bu durumlarda, düzen yeniden oluşturmayı tetiklemeyen Unity'nin yerleşik mekanizmalarını değiştirmek zordur.
İşte burada AltKanvaslar devreye giriyor. Değişken bir öğeyi bir AltKanvas içinde kapsüllediğimizde sonuçları inceleyelim.
8 elemandan oluşan bir düzen grubu oluşturacağız, bunlardan biri SubCanvas içinde yer alacak ve ardından dönüşümünü değiştireceğiz.
SubCanvas'taki RectTransform değişiklikleri:
Sonuçların da gösterdiği gibi, tek bir öğeyi bir AltCanvas içinde kapsüllemek, düzen üzerindeki yükü neredeyse ortadan kaldırıyor; bunun nedeni, AltCanvas'ın tüm değişiklikleri izole ederek hiyerarşinin daha yüksek seviyelerinde yeniden oluşturmayı engellemesidir.
Ancak, tuval içindeki değişikliklerin tuvalin dışındaki öğelerin konumlandırılmasını etkilemeyeceğini belirtmek önemlidir. Bu nedenle, öğeleri çok fazla genişletirsek, komşu öğelerle çakışma riski vardır.
8 adet düzen elemanını bir SubCanvas'a sararak devam edelim:
Önceki örnek, düzen üzerindeki yük düşük kalırken, toplu güncellemenin iki katına çıktığını göstermektedir. Bu, öğeleri birden fazla Alt Kanvas'a bölmenin düzen oluşturma üzerindeki yükü azaltmaya yardımcı olmasına rağmen, toplu montaj üzerindeki yükü artırdığı anlamına gelir. Sonuç olarak, bu bizi genel olarak net bir negatif etkiye götürebilir.
Şimdi başka bir deney yapalım. İlk olarak 8 elemanlı bir düzen grubu oluşturacağız ve sonra animatörü kullanarak düzen elemanlarından birini değiştireceğiz.
Animatör, RectTransform'u yeni bir değere ayarlayacaktır:
Burada, her şeyi manuel olarak değiştirdiğimiz ikinci örnektekiyle aynı sonucu görüyoruz. Bu mantıklıdır çünkü RectTransform'u değiştirmek için ne kullandığımızın bir önemi yoktur.
Animatör RectTransform'u benzer bir değere değiştirir:
Animatörler daha önce, her karede aynı değeri sürekli olarak üzerine yazdıkları bir sorunla karşı karşıyaydı, bu değer değişmeden kalsa bile. Bu, yanlışlıkla bir düzen yeniden oluşturmayı tetiklerdi. Neyse ki, Unity'nin daha yeni sürümleri bu sorunu çözdü ve alternatife geçme ihtiyacını ortadan kaldırdı
Şimdi, 8 elemanlı bir düzen grubunda metin değerinin değiştirilmesinin nasıl davrandığını ve düzenin yeniden oluşturulmasını tetikleyip tetiklemediğini inceleyelim:
Yeniden yapılanmanın da tetiklendiğini görüyoruz.
Şimdi 8 elemanlı düzen grubunda TextMechPro değerini değiştireceğiz:
TextMechPro ayrıca bir düzen yeniden oluşturmayı tetikler ve hatta normal Text'e göre toplu işleme ve işleme üzerinde daha fazla yük bindiriyor gibi görünmektedir.
8 elemanlı bir düzen grubunda SubCanvas'taki TextMechPro değerini değiştirme:
SubCanvas değişiklikleri etkili bir şekilde izole ederek düzenin yeniden oluşturulmasını engelledi. Yine de, toplu güncellemelerdeki yük azalmış olsa da, nispeten yüksek kalmaya devam ediyor. Bu, her harfin ayrı bir doku olarak ele alınması nedeniyle metinle çalışırken bir endişe haline geliyor. Sonuç olarak metni değiştirmek birden fazla dokuyu etkiliyor.
Şimdi, bir GameObject'i (GO) düzen grubu içerisinde açıp kapatırken oluşan yükü değerlendirelim.
8 elemandan oluşan bir düzen grubunun içindeki bir GameObject'i açma ve kapatma:
Gördüğümüz gibi, bir GO'yu açıp kapatmak aynı zamanda bir düzen yeniden oluşturma işlemini de tetikliyor.
8 elemanlı bir düzen grubuna sahip bir SubCanvas'ın içinde bir GO'yu açma:
Bu durumda SubCanvas da yükün hafifletilmesine yardımcı olur.
Şimdi, bir düzen grubuyla tüm GO'yu açıp kapatırsak yükün ne olacağını kontrol edelim:
Sonuçların gösterdiği gibi, yük şimdiye kadarki en yüksek seviyesine ulaştı. Kök öğeyi etkinleştirmek, alt öğeler için bir düzen yeniden oluşturmayı tetikler ve bu da hem toplu işleme hem de işleme üzerinde önemli bir yüke neden olur.
Peki, aşırı yük oluşturmadan tüm kullanıcı arayüzü öğelerini etkinleştirmemiz veya devre dışı bırakmamız gerekirse ne yapabiliriz? GO'nun kendisini etkinleştirmek ve devre dışı bırakmak yerine, Canvas veya Canvas Group bileşenini devre dışı bırakabilirsiniz. Ek olarak, Canvas Group'un alfa kanalını 0 olarak ayarlamak, performans sorunlarından kaçınırken aynı etkiyi sağlayabilir.
Canvas Group bileşenini devre dışı bıraktığımızda yüke ne olur? Canvas devre dışıyken GO etkin kaldığından düzen korunur ancak basitçe görüntülenmez. Bu yaklaşım yalnızca düşük bir düzen yüküyle sonuçlanmakla kalmaz, aynı zamanda toplu işleme ve işleme üzerindeki yükü de önemli ölçüde azaltır.
Şimdi, düzen grubundaki SiblingIndex'i değiştirmenin etkisini inceleyelim.
8 elemanlı bir düzen grubunda SiblingIndex'i değiştirme:
Gözlemlendiği üzere, düzenin güncellenmesi için yük 0,7 ms'de önemli olmaya devam ediyor. Bu, SiblingIndex'teki değişikliklerin bir düzen yeniden oluşturmayı da tetiklediğini açıkça gösteriyor.
Şimdi farklı bir yaklaşım deneyelim. SiblingIndex'i değiştirmek yerine, düzen grubundaki iki öğenin dokularını değiştireceğiz.
8 elemandan oluşan bir düzen grubunda iki elemanın dokularının değiştirilmesi:
Gördüğümüz gibi durum iyileşmedi; hatta daha da kötüleşti. Dokuyu değiştirmek aynı zamanda bir yeniden inşayı tetikler.
Şimdi, özel bir düzen grubu oluşturalım. 8 eleman oluşturacağız ve sadece ikisinin pozisyonlarını değiştireceğiz.
8 elemanlı özel düzen grubu:
Yük gerçekten de önemli ölçüde azaldı ve bu bekleniyordu. Bu örnekte, betik yalnızca iki öğenin konumlarını değiştirerek ağır GetComponent işlemlerini ve tüm öğelerin konumlarını yeniden hesaplama gereksinimini ortadan kaldırır. Sonuç olarak, toplu işlem için daha az güncelleme gerekir. Bu yaklaşım tek başına bir çözüm gibi görünse de, betiklerde hesaplamalar yapmanın genel yüke de katkıda bulunduğunu belirtmek önemlidir.
Düzen grubumuza daha fazla karmaşıklık kattıkça, yük kaçınılmaz olarak artacaktır, ancak hesaplamalar betiklerde gerçekleştiği için Düzen bölümüne yansımayacaktır. Bu nedenle, kodun verimliliğini kendimiz izlemek çok önemlidir. Ancak, basit düzen grupları için özel çözümler mükemmel bir seçenek olabilir.
Düzeni yeniden oluşturmak önemli bir zorluk teşkil eder. Bu sorunu ele almak için, değişebilen temel nedenlerini belirlemeliyiz. Düzeni yeniden oluşturmaya yol açan birincil faktörler şunlardır:
Unity'nin yeni sürümlerinde artık sorun teşkil etmeyen ancak eski sürümlerde sorun teşkil eden birkaç hususu vurgulamak önemlidir: aynı metnin üzerine yazmak ve animatörle aynı değeri tekrar tekrar ayarlamak.
Artık bir düzen yeniden oluşturmayı tetikleyen faktörleri belirlediğimize göre, çözüm seçeneklerimizi özetleyelim:
Yeniden oluşturmayı tetikleyen bir GameObject'i (GO) bir Alt Kanvas'a sarın. Bu yaklaşım değişiklikleri izole ederek hiyerarşideki diğer öğeleri etkilemelerini önler. Ancak dikkatli olun — çok fazla Alt Kanvas, toplu iş yükünü önemli ölçüde artırabilir.
GO yerine SubCanvas veya Canvas Group'u açın ve kapatın. Yeni GO'lar oluşturmak yerine bir nesne havuzu kullanın. Bu yöntem, düzeni bellekte koruyarak öğelerin yeniden oluşturulmasına gerek kalmadan hızlı bir şekilde etkinleştirilmesini sağlar.
Gölgelendirici animasyonlarını kullanın. Gölgelendirici kullanarak dokuyu değiştirmek bir düzen yeniden oluşturmayı tetiklemeyecektir. Ancak, dokuların diğer öğelerle çakışabileceğini unutmayın. Bu yöntem, SubCanvases'ı kullanmakla aynı amaca etkili bir şekilde hizmet eder, ancak bir gölgelendirici yazmayı gerektirir.
Unity'nin düzen grubunu özel bir düzen grubuyla değiştirin. Unity'nin düzen gruplarıyla ilgili temel sorunlardan biri, her LayoutElement'in yeniden oluşturma sırasında GetComponent'ı çağırması ve bu da kaynak yoğun olmasıdır. Özel bir düzen grubu oluşturmak bu sorunu çözebilir, ancak kendi zorlukları vardır. Özel bileşenlerin etkili kullanım için anlamanız gereken belirli operasyonel gereksinimleri olabilir. Bununla birlikte, bu yaklaşım özellikle daha basit düzen grubu senaryoları için daha verimli olabilir.