Unity Optimizasyon Önerileri

Yayınlandı: 19 Haziran 2016 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

SON GÜNCELLEME: 31 Ocak 2020 (Ders büyük ölçüde güncellendi)

Yine ve yeniden merhabalar,

Bu yazıda, kimilerini kendim tecrübe ettiğim, kimilerini de ordan burdan derlediğim Unity 3D optimizasyon tekniklerinden bahsedeceğim. Yeni şeyler öğrendikçe bu yazıyı güncellemeye çalışacağım.

Optimizasyon çok ucu açık bir şey olduğu için, kimsenin “optimizasyon konusuna hakimim” gibi bir söylemde bulunabileceğini sanmıyorum. Tam olarak da bu yüzdendir ki, kendi bildiğiniz optimizasyon tekniklerini de bu yazı altında yorum olarak paylaşırsanız, burada Türk oyun geliştiricileri için faydalı bir kaynak oluşturabiliriz diye ümit ediyorum.

Genel Kural: Oyununuzda sistemi en çok hangi bileşen(ler)in yorduğunu görmek için her daim Profiler kullanın. Profiler dersim için: https://yasirkula.com/2020/03/26/unity-profiler-kullanimi/

Yazıdaki optimizasyon tekniklerinin bazılarının başında [1], [2] gibi referanslar bulunmakta; bu referanslara yazının sonundaki “Yararlanılan Kaynaklar” kısmından ulaşarak, o optimizasyon tekniklerinin bahsi geçtiği orijinal dokümanları inceleyebilirsiniz.

Bazı optimizasyon tekniklerinde Garbage Collector (GC) terimini kullandım, bundan kısaca bahsetmek istiyorum. C++’ın aksine C#’ta, artık kullanılmayan hafıza otomatik olarak boşaltılır (automatic memory management). Bu işlemi Garbage Collector gerçekleştirir. Kullanılmayan hafızanın temizlenmesi önemlidir çünkü aksi taktirde bu hafıza tekrar kullanılamaz ve oyununuzun kullandığı RAM gitgide artar, ta ki oyun artık tüm RAM’i harcayıp çökene kadar. Garbage Collector çalıştığında, oyunun kullandığı tüm RAM’in üzerinden geçer ve RAM’in artık kullanılmayan kısımlarını bularak buraları temizler. Bu işlem biraz zaman alır ve bu esnada oyun donar (bu donma süresi, oyunun kullandığı RAM miktarı ile doğru orantılıdır); bu yüzden de garbage collector’ın olabildiğince az çalışması elzemdir. Peki garbage collector ne zaman çalışır? Oyuna atanmış olan RAM ağzına kadar doluyken, yeni bir obje için (Instantiate edilmiş bir obje, yeni bir array vs.) biraz daha hafıza gerektiğinde. İşte bu durumda garbage collector çalışır, kullanılmayan hafıza temizlenir ve eğer yeni oluşturulan objenin sığabileceği kadar yer temizlendiyse obje hafızanın o kısmını kullanmaya başlar. Yeteri kadar yer temizlenemediyse mecburen oyunun kullandığı RAM miktarı artar; bu da sonraki garbage collector operasyonlarının daha uzun sürmesi anlamına gelir. Tabi oyun RAM’i zaten tamamen kullanıyor idiyse o zaman maalesef oyun çöker (devasa bir oyun yapmıyorsanız buna çok kafanızı takmayın, bu sorun benim hiç başıma gelmedi). Özet geçecek olursak:

  • Instantiate ile veya new takısı ile oluşturduğunuz objeler, array’ler vs. hafızada yer kaplar, eğer hafıza doluysa garbage collector çalışır. Buradaki new olayı aslında biraz daha karmaşık bir konu çünkü new int[] ile oluşturulan bir array garbage collector’ı çalıştırırken, new Vector3 ile oluşturulan bir vektör çalıştırmaz çünkü Vector3’ün kullandığı hafıza, garbage collector’ın çalıştığı heap‘te değil, ayrı bir hafıza olan stack‘te tutulur (dediğim gibi bu biraz karmaşık bir konu, siz sadece Unity’de çokça kullandığımız new Vector3‘ün herhangi bir sıkıntı olmadığını ve garbage collector’a bir etkisi olmadığını bilin yeter).
  • Garbage collector çalışırken oyun tamamen donar, bu süre oyunun kullandığı RAM miktarı ile doğru orantılı olarak artar.

Profiler penceresinin GC Alloc sütunu, o frame’de ne kadar yeni hafıza kullanıldığını gösterir. Özellikle mobil oyunlarda, bu değer oyunun büyük çoğunluğunda 0 byte olmalıdır. Eğer her frame’de belli bir miktar hafıza kullanılıyorsa, garbage collector’ın olabildiğince az çalışması için, hafızayı kullanan kodu Profiler ile tespit edin ve o kodu 0 byte‘a düşürecek şekilde optimize edin (diyelim sürekli yeni bir array oluşturuluyorsa, onun yerine bu array’i oyunun başında bir kere oluşturup tekrar tekrar kullanın).

Script Optimizasyonu

  • [16] Scriptlerinizde içi boş Unity fonksiyonları bırakmayın (özellike Update, LateUpdate ve FixedUpdate). Bu fonksiyonlar Unity Script Reference‘de MonoBehaviour altında Messages olarak listelenir ve scriptlerinizde bu fonksiyonlardan herhangi birisi varsa, o fonksiyon içi boş olsa bile çağrılır. Update, LateUpdate ve FixedUpdate fonksiyonları oyun sırasında defalarca kez çağrıldığı için de özellikle bu fonksiyonlar konusunda özen gösterin: fonksiyonu kullanmıyor musunuz? O zaman kodunuzdan silin.
  • Kodunuzda bir component’e birden çok kez erişiyorsanız, o component’e her seferinde GetComponent ile erişmek yerine onu bir değişkenin içinde tutun ve sonraki seferlerde bu değişkeni kullanın.

Optimizasyon öncesi:

void FixedUpdate()
{
    // Yukarı yönde güç uygula
    GetComponent<Rigidbody>().AddForce( Vector3.up );
}
 
public void MuzikCal()
{
    GetComponent<AudioSource>().Play();
}

Optimizasyon sonrası:

private Rigidbody cachedRigidbody;
private AudioSource cachedAudioSource;
 
void Awake()
{
    cachedRigidbody = GetComponent<Rigidbody>();
    cachedAudioSource = GetComponent<AudioSource>();
}

void FixedUpdate()
{
    // Yukarı yönde güç uygula
    cachedRigidbody.AddForce( Vector3.up );
}
 
public void MuzikCal()
{
    cachedAudioSource.Play();
}
  • [15] GameObject.Find ile sahnedeki bir objeye erişmek yavaş bir işlemdir. Bunun daha optimize versiyonu GameObject.FindWithTag‘dır. Ancak daha daha iyi bir optimizasyon, ilgili objeyi bir değişkende tutmaktır. Özellikle Object.FindObjectOfType kullanıyorsanız bu optimizasyon çok daha önem arz etmektedir.

Optimizasyon öncesi:

void Update()
{
    // Bu objeyi, Obje2'nin olduğu yere ışınla
    transform.position = GameObject.Find( "Obje2" ).transform.position;
}
 
void Obje3uDeaktifEt()
{
    GameObject.FindWithTag( "Obje3unTagi" ).SetActive( false );
}

Optimizasyon sonrası:

private Transform obje2ninTransformu;
private GameObject obje3;
 
void Awake()
{
    obje2ninTransformu = GameObject.Find( "Obje2" ).transform;
    obje3 = GameObject.FindWithTag( "Obje3unTagi" );
}
 
void Update()
{
    // Bu objeyi, Obje2'nin olduğu yere ışınla
    transform.position = obje2ninTransformu.position;
}
 
void Obje3uDeaktifEt()
{
    obje3.SetActive( false );
}

NOT: Camera.main değişkeni, sahnedeki kameraya kolayca erişebilmenize olanak sağlar ama arkaplanda GameObject.FindWithTag("Main Camera") fonksiyonunu çağırır. Yani Camera.main’e sıklıkla erişiyorsanız, sonucu bir değişkene atıp o değişkeni kullanın.

  • GetComponents veya GetComponentsInChildren fonksiyonları, belli bir türdeki tüm component’leri bulup bir array olarak döndürürler. Her yeni array, garbage collector için biraz daha iş demektir. Bunun yerine, bu fonksiyonların List parametre alan versiyonlarını kullanın. Bu şekilde, bulunan sonuçlar halihazırda var olan List’te depolanır, yeni bir array oluşmamış olur.

Optimizasyon öncesi:

void ColliderlariKapat()
{
	Collider[] colliderlar = GetComponents<Collider>();
	for( int i = 0; i < colliderlar.Length; i++ )
		colliderlar[i].enabled = false;
}

Optimizasyon sonrası:

private readonly List<Collider> colliderlar = new List<Collider>();

void ColliderlariKapat()
{
	GetComponents<Collider>( colliderlar );
	for( int i = 0; i < colliderlar.Count; i++ )
		colliderlar[i].enabled = false;
}
  • Bazen oyun boyunca bir objeyi sürekli Instantiate ve Destroy etmeniz gerekebilir (örneğin bir FPS oyunundaki mermi prefabı veya bir patlama partikül efekti). Ancak bu Instantiate ve Destroy komutlarının çok fazla kullanımı performans açısından iyi bir şey değil. Tam olarak da böyle durumlar için pooling pattern dediğimiz bir teknik geliştirilmiş. Bunun çalışma prensibi basit: sürekli kullandığınız bir prefab’ın klonunu Destroy etmek yerine deactivate edip pool’a (havuz) atıyorsunuz. Daha sonradan bu prefab’ın başka bir klonuna ihtiyaç duyduğunuzda, önce havuzda hazırda bir klon var mı diye bakıyorsunuz; varsa havuzdaki klonu kullanıyorsunuz. Havuzda klon yoksa ancak o zaman yeni bir klon Instantiate ediyorsunuz. Bu basit teknik, FPS oyunlarından tutun infinite runner oyunlarına kadar pek çok oyun türünde kullanılmaya müsaittir. Daha fazla detay için şu derse göz atabilirsiniz: https://yasirkula.com/2019/10/30/unity-pooling-obje-havuzu-patterni/
  • Oyununuzun başında Application.targetFrameRate‘e bir değer verin. Bu değişken, oyunun hedeflediği FPS değerini belirler. Diyelim değerini 60 yaparsanız, oyun 60 FPS’in üzerine çıkmaz. Oyunun 120 FPS’te çalıştırılmasına göre 60 FPS’te çalıştırılması, özellikle mobil cihazlarda bataryayı daha yavaş harcamaya, cihazın daha yavaş ısınmasına ve CPU’nun daha az yorulmasına yardımcı olur. Mobil cihazlarda targetFrameRate’i mümkünse 30, oyununuz göze çok akıcı gelmiyorsa 45, süper akıcılık istiyorsanız da 60 yapın. Daha da yüksek bir rakamın size bir faydası olmayacaktır.
  • Vector struct’larında (mesela Vector3) Distance adında bir fonksiyon ve magnitude adında bir değişken bulunmakta. Bu iki komut da esasında bir vektörün uzunluğunu ölçmeye yarıyor. Ancak hesaplama sırasında karekök kullanmak gerektiğinden aslında bu işlem sanıldığından daha zahmetli bir şey. Eğer ki bu uzunluk değerini direkt kullanıcıya göstermeyecekseniz (örneğin iki vektörün uzunluğunu kıyaslamaya çalışıyorsanız), o zaman daha hızlı çalışan sqrMagnitude değişkenini kullanmayı düşünebilirsiniz. Bu değişken, bir vektörün uzunluğunun karesini döndürür ve bu değeri hesaplamak magnitude’a (ve Distance’a) göre daha hızlıdır.

Optimizasyon öncesi:

public float maksimumMesafe = 5f;
Vector3 vektor1, vektor2, vektor3;
  
void BirFonksiyon()
{
    // vektor1 ve vektor2 arasındaki mesafe maksimumMesafe'den küçükse
    if( Vector3.Distance( vektor1, vektor2 ) <= maksimumMesafe )
    {
        // bla bla 
    }

    // vektor3'ün uzunluğu 8.0'dan büyükse 
    if( vektor3.magnitude > 8f )
    {
        // bla bla
    }
}

Optimizasyon sonrası:

public float maksimumMesafe = 5f;
private float maksimumMesafeKaresi;
Vector3 vektor1, vektor2, vektor3;
 
void Start()
{
    maksimumMesafeKaresi = maksimumMesafe * maksimumMesafe;
}
 
void BirFonksiyon()
{
    // vektor1 ve vektor2 arasındaki mesafe maksimumMesafe'den küçükse
    if( ( vektor1 - vektor2 ).sqrMagnitude <= maksimumMesafeKaresi )
    { 
        // bla bla
    }

    // vektor3'ün uzunluğu 8.0'dan büyükse
    if( vektor3.sqrMagnitude > 64f )
    {
        // bla bla
    }
}
  • Eğer kodlarınızda bir sayıyı veya değişkeni sürekli belli bir sayıya bölüyorsanız; o zaman bu bölme işlemini bir çarpma işlemi ile değiştirin çünkü çarpma işlemi bölme işlemine göre çok daha hızlı hesaplanır.

Optimizasyon öncesi:

public float surtunme = 2f;

void Update()
{
    // Objeyi ileri yönde hareket ettir
    transform.Translate( Vector3.forward * Time.deltaTime / surtunme );
}

Optimizasyon sonrası:

public float surtunme = 2f;
private float _1BoluSurtunme;

void Start()
{
    _1BoluSurtunme = 1f / surtunme;
}

void Update()
{
    // Objeyi ileri yönde hareket ettir
    transform.Translate( Vector3.forward * Time.deltaTime * _1BoluSurtunme );
}
  • [15] SendMessage/BroadcastMessage fonksiyonları performans için hiç iyi değildir ve bu yüzden kullanılmamalıdır. Onun yerine, çağırmak istediğiniz fonksiyon hangi objedeyse o objeyi bir değişkende tutup, bu değişken vasıtasıyla fonksiyonu direkt olarak çağırmak “çok” daha hızlıdır.
  • foreach fonksiyonu, bir dizi objenin üzerinden hızlıca geçmek için ideal durabilir (başta ben de çok kullanıyordum). Ancak işin aslı, her foreach kullanımında arkaplanda bir IEnumerator objesi oluşturulur ve bu da Garbage Collector‘a fazladan yük anlamına gelir. Bu yüzden foreach kullanmak yerine, sadece bir satır fazla kod yazarak normal for kullanın.

Optimizasyon öncesi:

public Transform[] hareketEttirilecekObjeler;
 
void Update()
{
    foreach( Transform obje in hareketEttirilecekObjeler )
    {
        obje.Translate( Vector3.forward * Time.deltaTime );
    }
}

Optimizasyon sonrası:

public Transform[] hareketEttirilecekObjeler;
 
void Update()
{
    for( int i = 0; i < hareketEttirilecekObjeler.Length; i++ )
    {
        Transform obje = hareketEttirilecekObjeler[i];
        obje.Translate( Vector3.forward * Time.deltaTime );
    }
}
  • Eğer build alacağınız platform Mono yerine IL2CPP destekliyorsa (Player Settings‘teki Scripting Backend ayarından bunu öğrenebilirsiniz), IL2CPP ile build almayı deneyin. Mono’da alınan build’lar, script’leri intermediate language (IL) denilen formata çevirirken, IL2CPP’de alınan build’lar ise kodu bu IL formatından C++ (CPP) formatına çevirir (farkettiyseniz, IL2CPP’nin açılımı da “intermediate language to C++“tır). Genel olarak bu bedavadan performans artışı anlamına gelir. Android’de IL2CPP ile build alabilmek için NDK kurulu olması gereklidir, eğer kurulu değilse Unity sizi download linkine yönlendirecektir.
  • [5][14] public int Degisken { get; set; } şeklinde tanımlanmış property‘leri public int Degisken; şeklinde normal değişkenlere çevirin. Eğer değişkenin Inspector’da gözükmesini istemiyorsanız, başına [System.NonSerialized] attribute‘ü ekleyin.
  • [14] Bir fonksiyonda sürekli yeni bir List veya array oluşturuyorsanız, onun yerine bu List veya array’i oyunun başında bir kere oluşturup tekrar tekrar kullanın. Bu optimizasyon özellikle garbage collector‘ı çok sevindirecektir.
  • [14] Dokunmatik ekranlar için kod yazarken Input.touches kullanıyorsanız, onun yerine Input.touchCount ve Input.GetTouch kullanın. Dokunmatik ekran dersim için: https://yasirkula.com/2013/07/17/unity-ile-androide-uygulama-gelistirmek-1-dokunmatik-ekran-entegrasyonu/

Optimizasyon öncesi:

for( int i = 0; i < Input.touches.Length; i++ )
{
	Touch parmak = Input.touches[i];
}

Optimizasyon sonrası:

for( int i = 0; i < Input.touchCount; i++ )
{
	Touch parmak = Input.GetTouch( i );
}
  • [14] OnCollisionEnter vs. fonksiyonlara parametre olarak gelen Collision‘ın contacts değişkenini her kullandığınızda yeni bir array oluşturulur. Onun yerine, eğer Unity 2018.3 veya sonrasını kullanıyorsanız, contactCount ve GetContact kullanın (tıpkı Input.touches optimizasyonu gibi).
  • Bir objenin tag‘inin belli bir değere eşit olup olmadığına bakarken obje.tag == "Player" kodu yerine obje.CompareTag("Player") kodunu kullanın.
  • [15] Kodunuzun belli bir noktasında, pek çok string’i birleştirerek yeni bir string oluşturmanız gerekiyorsa, string’leri + ile birbirine eklemek yerine StringBuilder kullanın. Sadece iki string’i birleştirecekseniz + kullanmaya devam edin, üç veya dört string’i birleştirecekseniz string.Concat fonksiyonunu kullanın, daha fazlası için StringBuilder kullanın. Hatta eğer mümkünse her seferinde yeni bir StringBuilder objesi oluşturmak yerine, tek bir StringBuilder objesi oluşturup onu tekrar tekrar kullanın. Her kullanımdan sonra, StringBuilder.Length‘i 0’a eşitleyerek StringBuilder’ın içini boşaltın.

Optimizasyon öncesi:

public void StringBirlestirme( int skor, int para )
{
	string s1 = "Skor: " + skor;
	string s2 = "Para: " + para + "TL";
	string s3 = "Skor " + skor + " ve para " + para + "TL";
}

Optimizasyon sonrası:

// StringBuilder, kapasitesi string'i depolamaya yetmeyince
// otomatik olarak büyür ama dilersek StringBuilder'ın ilk kapasitesini
// objeyi oluştururken elle belirleyebiliriz (bu örnekte 100)
private readonly StringBuilder sb = new StringBuilder( 100 );

public void StringBirlestirme( int skor, int para )
{
	string s1 = "Skor: " + skor;
	string s2 = string.Concat( "Para: ", para, "TL" );
	string s3 = sb.Append( "Skor " ).Append( skor ).Append( " ve para " ).Append( para ).Append( "TL" ).ToString();
	sb.Length = 0; // Sonraki kullanımlar için StringBuilder'ın içini boşalt
}
  • [15] String.StartsWith veya String.EndsWith fonksiyonlarının tek bir string parametre alan versiyonlarını kullanıyorsanız, onlar yerine şu fonksiyonları kullanın:
// "asd".StartsWith( "a" ) yerine PerformansliStartsWith( "asd", "a" ) kullanın
bool PerformansliStartsWith( string a, string b )
{
	int aLen = a.Length;
	int bLen = b.Length;
	int ap = 0; int bp = 0;
	while( ap < aLen && bp < bLen && a[ap] == b[bp] )
	{
		ap++;
		bp++;
	}

	return ( bp == bLen );
}

// "asd".EndsWith( "a" ) yerine PerformansliEndsWith( "asd", "a" ) kullanın
bool PerformansliEndsWith( string a, string b )
{
	int ap = a.Length - 1;
	int bp = b.Length - 1;
	while( ap >= 0 && bp >= 0 && a[ap] == b[bp] )
	{
		ap--;
		bp--;
	}

	return ( bp < 0 );
}
  • [15] Bu maddeyi direkt örnek üzerinden göstereceğim.

Optimizasyon öncesi:

void Update()
{
	for( int i = 0; i < array.Length; i++ )
	{
		if( boolDegisken )
			BirFonksiyon( array[i] );
	}
}

Optimizasyon sonrası:

void Update()
{
	if( boolDegisken )
	{
		for( int i = 0; i < array.Length; i++ )
		{
			BirFonksiyon( array[i] );
		}
	}
}
  • [15] Update‘te çağırdığınız bir fonksiyonu, her frame’de çalıştırmak yerine 3-4 frame’de bir veya her 0.1 saniyede bir çalıştırmanız kafiyse (kullanıcı aradaki farkı anlamayacaksa), o fonksiyonu boş yere her frame’de çalıştırmayın.

Optimizasyon öncesi:

void Update()
{
	PahaliBirFonksiyon();
}

Optimizasyon sonrası:

void Update()
{
	if( Time.frameCount % 3 == 0 ) // Her 3 frame'de bir çalışır
		PahaliBirFonksiyon();
}

// VEYA

private float fonksiyonCagrilmaAni;
void Update()
{
	if( Time.time >= fonksiyonCagrilmaAni )
	{
		PahaliBirFonksiyon();
		fonksiyonCagrilmaAni = Time.time + 0.1f; // Her 0.1 saniyede bir çalışır
	}
}
  • [15] Bir kodun sadece obje kameranın görüş alanı içindeyse çalışmasını istiyorsanız, o kodu rendererComponenti.isVisible true ise çalıştırabilirsiniz. Görüş alanından kasıt, objenin kameranın field of view‘ının içinde olmasıdır, yani objenin bir duvarın arkasında gizleniyor olması isVisible’ın değerine etki etmez.
  • [14][16] LINQ kullanmayın. LINQ performans olarak hiç iyi değildir. Gerekirse biraz daha fazla kod yazın ama LINQ’ten kaçının.
  • [14] Regular Expression‘ları mümkünse kullanmayın, illa kullanmanız gerekiyorsa da bu regular expression’ı new Regex ile oluşturup bir değişkende tutun ve regular expression’ın sağlanıp sağlanmadığına regexDegisken.Match fonksiyonuyla bakın; yani static olan Regex.Match fonksiyonunu kullanmayın.
  • [8] transform.SetParent fonksiyonunu gereksiz kullanmayın; bu fonksiyonun sık kullanımı kolaylıkla performansı düşürebilir. Bu yüzdendir ki Instantiate fonksiyonunun Transform parent parametre alan versiyonları bulunmaktadır: eğer bir objeyi Instantiate edip hemen ardından SetParent ile parent’ını değiştiriyorsanız, bu parent belirleme kısmını direkt Instantiate’in içinde yaparak performansı biraz daha iyileştirebilirsiniz. Eğer object pooling tekniğinden faydalanıyorsanız da, objeleri havuza eklerken mümkünse SetParent kullanmayın.
  • transform.position ve transform.eulerAngles yerine mümkünse transform.localPosition ve transform.localEulerAngles kullanın. İlk saydıklarım objenin global konum ve rotasyonunu döndürürken, ikinci saydıklarım ise objenin local konum ve rotasyonunu döndürürler (Inspector’da Position ve Rotation olarak gözüken değerler aslında ikinci saydığım değerlerdir). position ve eulerAngles değerleri, objenin parent‘larının Position, Rotation ve Scale değerleriyle beraber değiştiği için, position ve eulerAngles’ı hesaplarken parent’ların Transform’larının da hesaba dahil olması gerekir; bu hesaplama hiçbir zaman localPosition ve localEulerAngles kadar hızlı değildir. Eğer position ve eulerAngles’ına erişmek istediğiniz objenin bir parent’ı olmadığını veya tüm parent’larının (parent’ının parent’ı vs.) 0,0,0 Position, Rotation ve Scale değerlerine sahip olduğunu kesin olarak biliyorsanız, o zaman localPosition ve localEulerAngles kullanın.
  • [15] Bir önceki maddede değindiğim gibi, transform.position ve transform.eulerAngles‘ı hesaplamak nispeten yavaştır. Bu yüzden bu değerlere mümkün olduğunca az erişmeye çalışın (örneğe bakınca anlayacaksınız).

Optimizasyon öncesi:

private void Update()
{
	// Objeyi daima Y=10 koordinatında tut
	// transform.position'a 2 kere erişiyoruz: x ve z koordinatlarını alırken
	transform.position = new Vector3( transform.position.x, 10f, transform.position.z );
}

Optimizasyon sonrası:

private void Update()
{
	// Bu sefer transform.position'a sadece bir kere erişiyoruz
	Vector3 konum = transform.position;
	konum.y = 10f;
	transform.position = konum;
}
  • [3] Hem transform.position‘a hem de transform.rotation‘a (veya transform.eulerAngles‘a) aynı anda değer veriyorsanız, bunun için transform.SetPositionAndRotation fonksiyonunu kullanın.

Optimizasyon öncesi:

private void Update()
{
	obje1.transform.position = obje2.transform.position;
	obje1.transform.eulerAngles = new Vector3( 0f, 90f, 0f );
}

Optimizasyon sonrası:

private void Update()
{
	obje1.transform.SetPositionAndRotation( obje2.transform.position, Quaternion.Euler( new Vector3( 0f, 90f, 0f ) ) );
}
  • [3] Bir kodu sadece objenin Transform‘u değiştiyse çalıştırmak istiyorsanız (örneğin konumu değiştiyse), kodu o Transform’un hasChanged değeri true olduğunda çalıştırın. Bir Transform’un herhangi değeri değiştiğinde, o Transform’un hasChanged’i otomatik olarak true olur. Ancak hasChanged true ise kodunuzu çalıştırdıktan sonra, hasChanged’i elle false yapın çünkü Unity bu değeri kendisi false yapmaz.
  • [12][14][16] Oyununuzun bazı parametrelerini bir JSON veya XML dosyasından çekiyorsanız, onun yerine ScriptableObject kullanmayı düşünebilirsiniz. ScriptableObject’in dezavantajı, içinde saklanan değerlerin oyunu build aldıktan sonra değiştirilememesidir ancak bu sizin için bir sıkıntı değilse, ScriptableObject kullanarak daha performanslı bir şekilde parametreleri okuyabilirsiniz. Kullanıcının parametreleri değiştirebilmesini istediğiniz için özellikle JSON kullanıyorsanız, bu JSON’u parse etmek için mümkünse Unity’nin JsonUtility sınıfını kullanın. JsonUtility dersim için: https://yasirkula.com/2020/04/03/unity-jsonutility-kullanimi/
  • Mobil oyunlarınızda, script’lerinizde OnMouseDown gibi fonksiyonlar bırakmayın. Bu fonksiyonlar varken build alırsanız Unity konsola bir uyarı basarak bunun mobilde performansı düşürebileceğinden yakınacaktır. İlaveten, Input.GetMouseButtonDown gibi içerisinde Mouse kelimesi geçen bir Input fonksiyonu veya değişkeni kullanmıyorsanız, oyunun başında Input.simulateMouseWithTouches = false; yapın.
  • [14] Materyallerin SetColor veya SetTexture gibi fonksiyonları ilk parametre olarak bir string veya int kabul eder. Eğer bu fonksiyonları birden çok kez çağırıyorsanız, performans için int’li fonksiyonları tercih edin. “_Color” gibi herhangi bir string parametrenin int karşılığını öğrenmek için, Shader.PropertyToID fonksiyonunu kullanabilirsiniz. Bu fonksiyon aynı string değeri için daima aynı int değeri döndürür, o yüzden bu fonksiyonun döndürdüğü int’i oyunun başında bir değişkende tutun ki birden fazla kez bu fonksiyonu çağırmak zorunda kalmayın.
  • [14] Yukarıda bahsettiğim durum Animator için de geçerli. Animator’ın SetFloat veya SetBool gibi fonksiyonlarını da int parametre ile çağırın. Bu int’in değerini öğrenmek için ise, Animator.StringToHash fonksiyonunu kullanın.
  • Kodlarınızda kullandığınız Debug.Log‘ların oyunu build aldığınızda da çalışmaya devam ettiğini biliyor muydunuz? Android’de bu log’lara çeşitli yollarla bakabilirken iOS’ta ise Xcode’un konsolu ile bakabilirsiniz. Debug.Log’lar, özellikle sık kullanılması halinde (mesela Update‘te), oyunun performansına gözle görülür derecede olumsuz etki edebilirler. Bu durumda yapabileceğiniz üç alternatif var:
  1. Sadece editörde gözükmesini istediğiniz Debug.Log’ları #if UNITY_EDITOR ve #endif satırlarıyla çevreleyin:
#if UNITY_EDITOR
Debug.Log( "Bu log sadece editörde gözükecek" );
#endif
  1. [4][16] Hiçbir Debug.Log’un build alınan oyunda gözükmesini istemiyorsanız, şöyle bir Debug2 class’ı oluşturun:
using System.Diagnostics;
using Debug = UnityEngine.Debug;

public static class Debug2
{
	[Conditional( "UNITY_EDITOR" )] public static void Log( object message ) { Debug.Log( message ); }
	[Conditional( "UNITY_EDITOR" )] public static void LogWarning( object message ) { Debug.LogWarning( message ); }
	[Conditional( "UNITY_EDITOR" )] public static void LogError( object message ) { Debug.LogError( message ); }
	[Conditional( "UNITY_EDITOR" )] public static void LogException( System.Exception exception ) { Debug.LogException( exception ); }
}

Ardından artık Debug.Log yerine Debug2.Log fonksiyonunu kullanın. Bu fonksiyon System.Diagnostics.Conditional("UNITY_EDITOR") attribute’üne sahiptir, yani UNITY_EDITOR condition’ının sağlanmadığı durumlarda (build alırken), Debug2.Log fonksiyonları script’lerinizden otomatik olarak silinir.

  1. Debug.Log’ların oyunda gözükmesini istiyor ama stacktrace‘lerinin (o log’un olduğu fonksiyonu hangi fonksiyonların çağırdığını gösteren bilgi) gözükmesini istemiyorsanız, Player Settings‘te Logging‘in altında yer alan değerleri ScriptOnly‘den None‘a çekin. Yalnız bu durum editörde aldığınız log’ları da etkiler, o yüzden bu değişikliği sadece build alırken yapıp, build aldıktan sonra değişikliği geri almak isteyebilirsiniz. Burada işin güzel yanı, normal log’ların stacktrace’lerini gizleyip sadece Debug.LogError‘ların veya hata mesajlarının (Debug.LogException) stacktrace’lerinin gözükmesini sağlayabilirsiniz (hataların kaynağını daha kolay tespit edebilmek için). Bunun için Logging ayarlarında Error ve Exception‘ı ScriptOnly’de bırakmanız yeterli.
  • [1] Bir MeshRenderer veya SkinnedMeshRenderer objenin materyaline erişmek için iki değişken vardır: material ve sharedMaterial. İlki, objenin mevcut materyalini klonlar, bu klon materyali objeye atar ve ardından bu materyali döndürür; ikincisi ise objenin materyalini klonlamadan direkt döndürür. Her yeni materyal performans için kötü olduğu için, mümkün olduğunca material‘den kaçının. Örneğin objenin materyalini herhangi bir şekilde değiştirmeyecek ama sadece materyalin rengini vs. kontrol edeceksiniz daima sharedMaterial kullanın. Yok ama sahnedeki sadece belli bir objenin rengini değiştirecekseniz, o zaman material kullanabilirsiniz. Eğer materyalin alabileceği renkler belliyse (mesela ya kırmızı ya da beyaz), bunun daha iyi bir yolu, materyalin rengini değiştirmek yerine direkt materyali değiştirmektir. Yani elinizde aynı materyalin hem beyaz hem kırmızı versiyonu olur ve objenin direkt sharedMaterial‘inin değerini değiştirerek objenin rengini değiştirirsiniz. Maalesef sharedMaterial ile ilgili dikkat etmeniz gereken önemli bir husus var: editördeyken objenin sharedMaterial’ine yapılan değişiklikler, direkt Project’teki materyal asset’ini değiştirir, yani yaptığınız değişiklik Play moddan çıkınca geri alınmaz.
  • [6] Vektör hesaplaması yaparken işlem önceliğine dikkat ederek bedavadan optimizasyon yapabilirsiniz:
// İki (Vector3*float) hesaplaması var: YAVAŞ
Vector3 v1 = Vector3.forward * 5f * Time.deltaTime;

// Bir (float*float) ve bir (Vector3*float) hesaplaması var: DAHA HIZLI
Vector3 v2 = Vector3.forward * ( 5f * Time.deltaTime );
Vector3 v3 = 5f * Time.deltaTime * Vector3.forward;
  • [6] Bir List‘in elemanlarının üzerinden for döngüsü ile geçiyorsanız ve bu List’in eleman sayısı for’dayken değişmeyecekse, List.Count‘u sadece bir kere çağırmaya çalışın:

Optimizasyon öncesi:

float ListElemanlariniTopla( List<float> list )
{
	float sonuc = 0;
	for( int i = 0; i < list.Count; i++ )
		sonuc += list[i];

	return sonuc;
}

Optimizasyon sonrası:

float ListElemanlariniTopla( List<float> list )
{
	float sonuc = 0;
	for( int i = 0, elemanSayisi = list.Count; i < elemanSayisi; i++ )
		sonuc += list[i];

	// Veya list'i baştan sona değil sondan başa gez, Count yine bir kere kullanılır
	//for( int i = list.Count - 1; i >= 0; i-- )
	//	sonuc += list[i];

	return sonuc;
}
  • [15] StartCoroutine ile bir coroutine‘i başlatırken mutlaka bir miktar RAM kullanılır (garbage collector için kötü), gereksiz coroutine’lerden kaçının.
  • [15] Bir coroutine‘i bir frame bekletmek için yield return 0; kullanmayın, onun yerine yield return null; kullanın. İlk kod, boxing’den dolayı RAM harcar.
  • [15] Coroutine‘lerde kullanılan WaitForSeconds‘ı birden çok kez kullanabilirsiniz, her seferinde new WaitForSeconds ile yeni bir obje oluşturmanıza gerek yok.
  • [4] Kodunuzda 2 boyutlu array kullanıyorsanız, bu array’leri int[,] şeklinde değil de int[][] şeklinde tanımlayın.
  • [4] İnternetten veri indirmek için Unity’nin WWW sınıfını kullanıyorsanız, onun yerine UnityWebRequest kullanın. WWW her seferinde yeni bir thread oluştururken, UnityWebRequest’in kendi içinde bir thread havuzu bulunmaktadır.
  • [7] Eğer oyununuzu Profiler ile debug ettiğinizde, oyundaki takılmaların büyük bir kısmının garbage collector (GC) tarafından kaynaklandığını tespit ederseniz, Player Settings‘teki Use incremental GC seçeneğini (Unity 2019.1+) açıp oyunu bir de öyle test edin.
  • [5] Dictionary ve HashSet veri türlerinin tüm elemanlarının üzerinden foreach ile geçmek nispeten yavaştır. Eğer bu veri türlerinin sağladığı kolaylıklara özelliklere ihtiyacınız yoksa List kullanın veya aynı veriyi hem List hem de Dictionary/HashSet’te tutup tüm elemanların üzerinden geçerken List’i kullanın.
  • [5] Key‘i UnityEngine.Object türünde olan çok sık kullandığınız bir Dictionary varsa, key’in türünü int yapıp, bir sorgu yaparken de obje.GetInstanceID()‘i key olarak kullanın. Hatta mümkünse bu GetInstanceID’nin sonucunu cache’leyin, örneğin objelerinizi bir List<Object>‘te tutuyorsanız, bunlara karşılık gelen GetInstanceID’leri de bir List<int>‘te tutabilirsiniz.
  • [5] Key‘i bir enum türünde olan Dictionary‘ler, sorgulama esnasında hafızayı bir miktar harcarlar ve bu da garbage collector‘ı yorar. Bundan kaçınmak için Dictionary’i oluştururken bir IEqualityComparer kullanın.

Optimizasyon öncesi:

private enum TestEnum { A, B };
private readonly Dictionary<TestEnum, int> dict = new Dictionary<TestEnum, int>();

private void Update()
{
	int deger;
	dict.TryGetValue( TestEnum.A, out deger ); // Her seferinde 20 byte RAM harcar
}

Optimizasyon sonrası:

private class TestEnumComparer : IEqualityComparer<TestEnum>
{
	public bool Equals( TestEnum x, TestEnum y ) { return x == y; }
	public int GetHashCode( TestEnum obj ) { return (int) obj; }
}

private enum TestEnum { A, B };

// Dictionary'i oluştururken IEqualityComparer kullanıyoruz
private readonly Dictionary<TestEnum, int> dict = new Dictionary<TestEnum, int>( new TestEnumComparer() );

private void Update()
{
	int deger;
	dict.TryGetValue( TestEnum.A, out deger ); // Artık hiç RAM harcamaz
}
  • [8] Hierarchy’de sahnenin root’unda yer alan (parent’ı olmayan) tüm Transform‘ların hierarchyCapacity adında bir değişkeni bulunmaktadır. Unity sahnedeki Transform’ları kendi içinde şu şekilde barındırmaktadır: Her root Transform içerisinde List gibi bir yapı barındırır ve bu List’te de Transform’un tüm child objeleri yer alır (child objelerinin child objeleri, onların da child objeleri vs. bu listeye dahildir). Bu root Transform’a veya onun child’larından birine eklenen yeni bir child obje, root Transform’un içindeki bu List’e dahil olur. Eğer List ağzına kadar doluysa, List’in boyutu artar. Root Transform’ların sahip olduğu hierarchyCapacity, bu List’in kapasitesini belirlemektedir. Gelelim buradaki optimizasyona: eğer sahnedeki bir root Transform’a SetParent ile pek çok child obje ekleyecekseniz ve toplamda kaç child obje ekleyeceğinizi kabaca biliyorsanız, her birkaç SetParent’ta bir Transform’un hierarchyCapacity’sinin otomatik olarak artırılması yerine (kapasite her arttığında bu Garbage Collector’a yük olur), en başta root Transform’un hierarchyCapacity’sini elle belirleyerek otomatik olarak kapasitenin artmasının önüne geçebilirsiniz. Yalnız root Transform’a kaç child ekleyeceğinizi bileceğiniz gibi aynı zamanda her bir child’ın da kaç child’dan oluştuğunu bilmeniz ve hierarchyCapacity’i ona göre artırmanız lazım çünkü başta da dediğim gibi, root Transform’un içindeki List, onun tüm child’larını, child’larının child’larını, onların child’larını vs. kısaca objenin altında yer alan her şeyi içinde barındırır.
  • [11] ParticleSystem‘ın Play, Pause gibi fonksiyonları bool withChildren isminde opsiyonel bir parametre alırlar; bu parametre varsayılan olarak true‘dur. Parametre true olduğunda, ParticleSystem GetComponentsInChildren ile alt objelerindeki ParticleSystem component’lerini de bulur ve Play/Pause komutlarını onlara da uygular. Tahmin edeceğiniz üzere, GetComponentsInChildren kullanımından dolayı bu performans için iyi değildir. Eğer withChildren parametresinin değeri false ise, GetComponentsInChildren çağrılmaz ve Play/Pause fonksiyonu sadece mevcut ParticleSystem’a uygulanır. Eğer bir ParticleSystem’ın child’ı olmadığını biliyorsanız withChildren’ı daima false yapın, aksi taktirde ParticleSystem’ı ve içindeki tüm child’ları oyunun başında bir array’e alıp, Play/Pause yaparken array’deki tüm ParticleSystem’ların Play/Pause fonksiyonlarını withChildren false parametre ile tek tek çalıştırın.
  • [9] Bir AudioSource‘un sesini Mute ile kıssanız bile, AudioSource hâlâ müziği sessiz bir şekilde çalarak CPU yemeye devam eder. Mümkünse Stop fonksiyonu ile çalmakta olan sesi durdurun veya audioSource.enabled=false; ile Audio Source’u disable edin.
  • [14] Bir fonksiyon params şeklinde bir parametre alıyorsa (mesela params int[]), bu fonksiyonu her çağırdığınızda arkaplanda yeni bir array oluşturulur ve parametreleriniz bu array’e kopyalanır. Örneğin Mathf.Max ve Mathf.Min fonksiyonlarının 2’den fazla parametre alan versiyonları params kullanır. Bu, özellikle garbage collector için kötü olduğundan, bu fonksiyonları kullanmamaya çalışın.
  • [15] Garbage Collector‘u System.GC.Collect(); ile elle çalıştırabilirsiniz. Örneğin bir loading ekranı esnasında bu fonksiyonu çağırırsanız, garbage collector’un oyun esnasında çalışıp da oyunu dondurma şansını azaltırsınız.
  • [4] Çok uzun bir const string kullanıyorsanız, bunu static readonly string yapın. “const string” build esnasında kodda her kullanıldığı yere kopyala-yapıştır yapıldığı için, kodun şişmesine sebep olabilir.
  • [9] IL2CPP ile build alırken, compiler otomatik olarak kodunuza şu eklemeleri yapar (kaynak):
    • Bir objeye erişmeden önce, o objenin null olup olmadığına bakan bir if koşulu
    • Array’in herhangi bir elemanına erişmeden önce, o index’in array’in [0,array.Length) sınırları dahilinde olup olmadığına bakan bir if koşulu

IL2CPP kodunuzu C++’a çevirdiği için, bu if koşullarını otomatik olarak eklemeseydi, bu koşullardan birinde bir sıkıntı çıkınca (eriştiğiniz obje null ise veya array’in -1. index’teki elemanına erişmeye çalışırsanız) oyununuz çökerdi. Ancak bazen bu ekstra if koşullarının performans harcamaktan başka işe yaramadığı, yazdığınız kodun bu iki sıkıntıyı kesinlikle yaşamayacağını bildiğiniz durumlar olacaktır. Bu durumlarda isterseniz IL2CPP’nin kodunuzun o güvenli kısımlarına otomatik olarak if koşulları eklemesini engelleyebilirsiniz. Bunun için Unity’nizin kurulu olduğu konumdaki Editor\Data\il2cpp\Il2CppSetOptionAttribute.cs dosyasını kopyalayıp projenizin Assets klasörüne yapıştırın. Ardından şu iki attribute ile, istediğiniz class, fonksiyon veya property‘de bu if koşullarının oluşmasını engelleyebilirsiniz (kodun başına using Unity.IL2CPP.CompilerServices; da ekleyin):

private float[] array = new float[5];

[Il2CppSetOption( Option.NullChecks, false )] // Null kontrolünü kapat
[Il2CppSetOption( Option.ArrayBoundsChecks, false )] // Array index'inin sınırlar dahilinde olup olmadığı kontrolünü kapat
private float ArrayElemaniDondur( int index )
{
	return array[index];
}

Fizik Optimizasyonu

  • [10][16] Bazı oyunlarda fizik çok önemli bir yere sahipken bazı oyunlarda ise fiziksel etkileşimler minimum düzeydedir (hatta belki de hiç fizik kullanılmamaktadır). Fizik olaylarının hesaplaması FixedUpdate‘te yapılır ve FixedUpdate fonksiyonu da belli bir frekansta çalıştırılır. Eğer Edit-Project Settings-Time‘a girecek olursanız, fizik hesaplamalarının varsayılan olarak 0.02 saniyelik bir periyotla gerçekleştiğini görebilirsiniz (Fixed Timestep). Eğer ki oyununuzda fizik olayları çok büyük bir yere sahip değilse, bu değeri yavaş yavaş artırıp bunun oyununuzdaki fiziksel etkileşimlere olan etkisini kontrol edin. Bu değeri ne kadar artırırsanız, bu performans için o kadar iyidir ama eğer değeri çok artırırsanız da o zaman fiziksel etkileşimler istendiği gibi çalışmamaya başlar, o yüzden deneye yanıla ince ayar yapmanız en iyisi.
  • [10] Bir objenizde collider var ve bu obje oyun sırasında hareket mi ediyor? O zaman ya bu objede ya da bu objenin bir parent’ında bir Rigidbody de olduğundan emin olun (objenin fiziksel güçlerden [yerçekimidir, AddForce’dur vb.] etkilenmesini istemiyorsanız da Rigidbody’deki “Is Kinematic“i işaretleyin). Aksi taktirde Rigidbody’siz ama collider’lı bu obje her hareket ettiğinde, fizik motoru bazı ağır hesaplamalar yapmak zorunda kalır.
  • [10][16] Fizik objelerinizde mümkün olduğunca az Mesh Collider kullanın. Mesh Collider kullanan çoğu objenin şeklini aslında birkaç primitive collider (Box Collider, Sphere Collider ve Capsule Collider) kullanarak da yaklaşık olarak taklit edebilirsiniz (ki bu “yaklaşık” olarak taklit etme durumu, çoğu fiziksel etkileşim için yeterli olur). O halde napıyoruz? Objelerimizdeki Mesh Collider’ları, Unity’nin sağladığı primitive collider’lar ile taklit etmeye çalışıyoruz (ardından Mesh Collider component’ini silmeyi unutmayın). İhtiyaç halinde bu collider’ları, objenin child GameObject’lerine de verebilirsiniz (mesela collider’a biraz eğim vermeniz gerekiyorsa).
  • Raycast fonksiyonlarınızda, bu raycast ışını için bir maksimum uzunluk belirleyin. Çoğu durumda sonsuz uzunlukta bir raycast ışını yollamak yerine örneğin 100 birim uzunluğunda bir raycast ışını yollamak yeterlidir. Bir başka önemli husus ise layer mask kullanımı. Sahnedeki tüm objeler üzerinden raycast testi yapmak yerine sadece belli bir layer’daki objelere karşı raycast yaparsanız, tahmin edebileceğiniz üzere daha hızlı sonuç alırsınız. Örneğin bir karakterin havada mı yoksa yerde mi olduğunu kontrol etmek istiyorsanız gökyüzündeki bulutları raycast’e karıştırmanıza gerek yok, sadece zemin objelerine karşı raycast yapmanız yeterli. Bunun için zemin objelerine “Zemin” isminde bir layer verip bu layer’ı Raycast fonksiyonunuzda kullanabilirsiniz.

Optimizasyon öncesi:

// Karakter yerde mi kontrol ediyoruz
public bool IsGrounded()
{
    // Karakterin pozisyonundan aşağı yönde 1 birim uzunluğunda
    // raycast ışını yollayıp herhangi bir objeye çarpıp çarpmadığına bak
    return Physics.Raycast( transform.position, Vector3.down, 1f );
}

Optimizasyon sonrası:

// Bu değişkene Inspector'dan değer olarak "Zemin" layer'ı verilmeli
public LayerMask zeminLayer;
 
// Karakter yerde mi kontrol ediyoruz
public bool IsGrounded()
{
    // Karakterin pozisyonundan aşağı yönde 1 birim uzunluğunda
    // raycast ışını yollayıp herhangi bir zemin objesine çarpıp çarpmadığına bak
    return Physics.Raycast( transform.position, Vector3.down, 1f, zeminLayer );
}
  • Physics.RaycastAll ve Physics.OverlapSphere gibi fonksiyonlar her seferinde yeni bir array döndürerek cihazın hafızasını yavaş yavaş sömürürler (bu sömürme durumu Physics.Raycast için geçerli değil). Bu fonksiyonların çoğunun, hafızadan yemeyen NonAlloc isimli versiyonları bulunmaktadır: Physics.RaycastNonAlloc ve Physics.OverlapSphereNonAlloc. Bu NonAlloc fonksiyonlar parametre olarak bir RaycastHit[] array’i alırlar ve buldukları RaycastHit sonuçları bu array’de depolarlar. Fonksiyonun döndürdüğü int değeri ise, kaç RaycastHit bulunduğunu (haliyle array’e kaç RaycastHit eklendiğini) belirtir. Diyelim fonksiyona kapasitesi 10 olan bir RaycastHit[] array’i verirseniz ve fonksiyon 3 döndürürse, fonksiyonun döndürdüğü RaycastHit’ler array’in 0, 1 ve 2. index’lerinde depolanır. Eğer fonksiyona çok küçük bir array verirseniz (bu örnekte diyelim 2 elemanlı bir array), fonksiyon 2 RaycastHit bulduktan sonra otomatik olarak durur, diğer RaycastHit’leri bulmaz.
  • [16] Edit-Project Settings-Physics‘teki Layer Collision Matrix, hangi layer’larda yer alan objelerin birbiriyle temas edebileceğini belirler. Birbiriyle kesinlikle temas etmeyecek layer’ların tiklerini kaldırarak, boş yere bu layer’lar arası temas hesaplamaları yapılmasını engelleyebilirsiniz. Eğer 2D fizik kullanıyorsanız, aynı matrix Edit-Project Settings-Physics 2D‘de de mevcuttur.
  • [16] Edit-Project Settings-Physics‘teki Auto Sync Transforms‘u kapatın. Bu değer açık olduğunda, bir objenin Transform’u ne zaman değişirse fizik motoru bu değişikliği anında fizik dünyasına uygularken, bu değeri kapatırsanız tüm Transform değişiklikleri FixedUpdate’ten hemen önce toplu bir şekilde fizik dünyasına uygulanır. Oyununuzda çok fazla Rigidbody varsa ve Update’te sürekli Transform değerlerini değiştiriyorsanız, bu optimizasyonun performansa büyük etkisi olabilir.
  • [16] Edit-Project Settings-Physics‘teki Reuse Collision Callbacks‘i açın. Kodlarınızdaki OnCollisionEnter gibi fonksiyonların aldıkları Collision temas parametresi, her seferinde yeni bir Collision objesi oluşturarak yavaş yavaş hafızadan yer. Ancak eğer bu seçeneği açarsanız, fizik motoru tek bir Collision objesini tekrar tekrar kullanır, sürekli yeni Collision objesi oluşturmaz. Bu durumun tek dezavantajı, eğer Collision parametresini script’inizde daha sonra kullanmak üzere bir yerde depoluyorsanız, bu Collision objesinin içeriği bir sonraki temasta değişeceği için kodunuz düzgün çalışmayabilir. Ancak ben şimdiye kadar hiç Collision objesini depolama ihtiyacı hissetmediğimden, bu handikapın çoğu proje için önemsiz olduğunu düşünüyorum.

UI Optimizasyonu

  • [2] UI’ın daha performanslı çalışması ve sprite’larınızın daha az yer kaplaması için, UI’de sprite atlas kullanın. Neyse ki Unity bu konuda bize oldukça yardımcı oluyor. Edit-Project Settings-Editor‘de yer alan Sprite Packer‘ı Always Enabled‘a veya Enabled For Builds‘a çekin. İlki, Unity’nin editörde de sprite atlasları kullanmasını sağlarken ikincisi, sprite atlas’ların sadece oyunu build alırken oluşmasını sağlar; ben ilkini kullanıyorum. Bundan sonraki aşama, Unity sürümünüze göre değişiklik gösteriyor:
    • Unity 2017.1 ve sonrası: Project panelinde Create-Sprite Atlas ile yeni bir sprite atlas oluşturun. Allow Rotation ve Tight Packing değerlerini kapatın (UI’da sıkıntı çıkarabiliyor) ve sprite atlas’a eklemek istediğiniz sprite’ları, Objects for Packing kısmına ekleyin. Buraya bir klasör eklerseniz, klasörün içindeki tüm sprite’lar otomatik olarak sprite atlas’a dahil olur. Dilerseniz Pack Preview butonuna tıklayarak, oluşan sprite atlas’ı görüntüleyebilirsiniz.
    • Daha eski sürümler: Sprite Atlas’a eklemek istediğiniz sprite’ların Inspector’larındaki Packing Tag‘lerine istediğiniz gibi bir değer verin (mesela “UIAtlas“). Aynı Packing Tag’i taşıyan sprite’lar, otomatik olarak aynı sprite atlas’a eklenirler.
  • [5][13][16] Tüm UI objelerinizi tek bir Canvas‘ın içine yığmayın. Canvas’ın içindeki tek bir UI elemanının tek bir özelliği değişse (konumu, rengi vs.), tüm canvas yenilenmek zorunda kalır (evet, TÜM CANVAS). Örneğin dinamik UI objeleriniz ile statik (hiç değişmeyen) UI objelerinizi ayrı canvas’larda tutun. Dinamik UI objelerinizden bazıları çok sık değişiyor ama bazıları ender değişiyorsa, bu çok sık değişen UI objelerinizi de ayrı bir canvas’a alın. Burada işin güzel yanı, bu iki canvas’ın birbirinden tamamen bağımsız olmasına gerek yok, bir canvas objesi başka bir canvas objesinin child objesi olabilir; buna nested canvas denir. Parent canvas’taki bir UI objesi değişse bile, nested canvas’taki UI objelerinde bir değişiklik olmadığı sürece nested canvas yenilenmez, boş yere CPU harcanmaz. Nested canvas’larla ilgili dikkat etmeniz gereken tek şey, eğer nested canvas’ın içinde Button gibi, mouse veya parmak ile etkileşime girebilen bir UI objesi varsa, nested canvas’ta Graphic Raycaster component’i olduğundan emin olun; parent canvas’taki Graphic Raycaster nested canvas’ı etkilemez.
  • [2] Canvas’ınızın Hierarchy’sinde objelerinizin sırası Image-Text-Image-Text diye gidiyorsa, bunu mümkün olduğunca Image-Image-Text-Text şeklinde yeniden düzenlemeye çalışın. Yani Text’leri Hierarchy’de elinizden geldiğince alt alta tutmaya çalışın, araya Image vari başka bir component sokmayın. Bu şekilde Unity’nin UI’ı daha iyi optimize ederek draw call sayısını azaltma şansı daha fazla olacak.
  • [5][16] Horizontal Layout Group ve Grid Layout Group gibi Layout Group component’lerini mümkün olduğunca az kullanın, canvas her kendini yenilediğinde bu component’ler canvas’a ekstra yük olurlar. Arayüzünüzün düzenli olması için bu Layout Group’lar kritik öneme sahipse ama içerikleri dinamik değilse (bir kere oluştuktan sonra değişmeyecekse), arayüzünüzü ekranda gösterdikten sonra bu component’leri disable ederek (.enabled=false), canvas’ın bundan sonraki kendini yenileme sürecinde bu component’lerin boş yere aynı hesaplamaları yapmalarının önüne geçebilirsiniz.
  • Yeni bir Scroll View oluşturduğunuzda, oluşan objenin Viewport isimli child’ında Mask da dahil olmak üzere bir takım component’ler yer alır. Mask’ın amacı, scroll view’ın içeriğinin, Viewport’un dışında kalan kısmını ekrana çizdirmemektir. Artık aynı işi daha performanslı bir component ile yapmak mümkündür: Rect Mask 2D. Bu değişiklik için Viewport’taki RectTransform hariç tüm component’leri silip ardından Add Component ile objeye Rect Mask 2D component’i eklemeniz yeterli.
  • [2] Text component’inin Best Fit seçeneğini çok lüzumlu olmadıkça kullanmayın çünkü yazının font boyutunun hesaplamak için CPU’nun bir takım deneme yanılma yapması gerekmektedir.
  • (Unity 2018.3+) [1][16] İçerisinde çok fazla transparan piksel olan bir sprite arayüzde büyük bir yer kaplıyorsa, bu overdraw sıkıntısına zemin hazırlarlar. Overdraw, objelerin üst üste çizilmesinin GPU’yu zorlaması sıkıntısıdır. Ne kadar çok obje üst üste çizilirse, grafik performansı o kadar düşer. Image‘larınızda kullandığınız sprite‘lardaki transparan pikseller her ne kadar görünmez olsalar da, GPU tarafından işlendikleri için overdraw’dan kaçamazlar. Neyse ki Unity’nin son sürümleriyle beraber, Image component’ine Use Sprite Mesh değişkeni eklendi (yalnızca Image Type‘ın değeri Simple iken gözükür). Bu ayarı aktif ettiğinizde, artık Image ekrana bir quad (dikdörtgen) geometri ile çizilmek yerine, sprite import edilirken otomatik olarak oluşturulan optimize geometri ile çizilir (sprite’ın Inspector’undaki Mesh Type‘ın Tight olması lazım, oluşan geometri transparan piksellerin büyük kısmını dışarıda bırakır). Bu, ekrana biraz daha fazla vertex ve triangle çizilmesi anlamına gelir ama overdraw genelde vertex sayısından daha büyük bir sıkıntı olduğu için, bu bir sorun olmaz.
  • [16] Yukarıda bahsettiğim overdraw (objelerin üst üste çizilmesi) sıkıntısından dolayı, bir Image‘ın üzerine başka bir Image veya Text‘i olabildiğince az çizdirmeye çalışın. Örneğin Hearthstone vari bir kart oyununda, kartın her bir bileşenini ayrı bir Image veya Text yapmak, kartı kişiselleştirmeyi kolaylaştırır ama bir yandan da overdraw’u artırır. Böyle bir durumda, sizi de çok zora sokmayacak şekilde, üst üste çizilen sprite ve text’leri olabildiğince birleştirmeye, daha az Image ve Text kullanmaya çalışın. Örneğin her kartın PNG dosyası, o kartın üzerindeki yazıları vs. içine dahil ederse, tek bir Image ile kart ekrana çizilebilir.
  • [5] Canvas‘ın Pixel Perfect‘i ekstra performans harcar, lüzumlu değilse bu seçeneği kapalı tutun.
  • [5] Canvas‘larınızda Render Mode olarak Screen Space – Camera yerine mümkünse Screen Space – Overlay‘i tercih edin.
  • [5][13] Render Mode‘u World Space olan Canvas‘larınızın Event Camera‘sını asla boş bırakmayın, buraya kameranızı değer olarak verin. Aksi taktirde Unity arkaplanda defalarca kez Camera.main‘i çağırmak zorunda kalır.
  • [13][16] Mouse veya parmak ile herhangi bir şekilde etkileşime geçmeyen Image, Text vs. objelerin Raycast Target‘larını kapatın. Örneğin yeni bir Button oluşturunca, içinde otomatik olarak bir Text de oluşur; bu Text’te Raycast Target’a gerek yoktur.
  • [5][13] Bir Canvas‘ı gizlemek için SetActive(false) yerine enabled=false‘u tercih edin, canvas’ı bu şekilde gösterip gizlemek daha performanslıdır. Ancak enabled=false yaptığınızda, canvas’ın child objelerinde Update fonksiyonu olan script’ler varsa bunlar çalışmaya devam edecektir, bu yüzden Canvas’ın enabled’ını değiştirdikten hemen sonra bu script’lerin enabled’ını da değiştirmek isteyebilirsiniz.
  • [5][16] Scroll View‘ınızda çok fazla (yüzlerce veya binlerce) eleman varsa, sadece ekranda gözüken elemanlar için UI objesi oluşturan bir sistem kullanın (list view diye de geçer); aksi taktirde o yüzlerce eleman, canvas’ın sınırları dışında kalsalar bile bir miktar performans yerler. Örnek bir plugin: https://github.com/boonyifei/ScrollList. Başka hazır plugin’ler için Asset Store’da “List View” şeklinde arama yapabilir veya Google’da “unity reusable scroll view github“, “unity list view github” gibi aramalar yapabilirsiniz.
  • [5] Scroll Rect component’inin Inertia değeri açık olduğunda, scroll view’ı biraz kaydırıp parmağınızı çektiğinizde, scroll view hâlâ kaymaya devam eder ve bir süre sonra yavaşlayarak durur. Eğer Inertia kullanıyorsanız, Deceleration Rate‘i olabildiğince düşürün (mesela 0.001). Bu değer ne kadar düşerse, scroll view o kadar hızlı yavaşlayıp durur. Scroll view kaydığı sürece canvas sürekli kendini yenilemek zorunda olduğu için, bu süreyi kısaltmak canvas’ı rahatlatır.
  • [5] Eğer UI elemanlarınızdan bazılarında object pooling‘den faydalanıyorsanız ve bir UI objesini havuza atarken aynı zamanda SetParent yapıyorsanız (illa gerekli değilse yapmayın), SetParent’tan hemen önce objeyi inaktif yapın. Havuzdan objeyi çekerken ise önce SetParent yapın, ardından objede herhangi bir değişiklik yapacaksanız bu değişiklikleri yapın ve ancak ondan sonra objeyi aktif hale getirin.

Grafik Optimizasyonu

  • Oyununuzdaki texture’ların boyutu düşük olsun diye onları Unity’e .jpeg formatında atmayın. Ya da benzer şekilde, aslı 512×512 olan kaliteli bir texture’u, boyutu düşük olsun diye resim editörünüzden (Photoshop vb.) 256×256’ya ufaltıp öyle Unity’e yollamayın. Unity’e elinizdeki en kaliteli kaynak dosyayı yollayın (örneğin .psd veya .png ve 512×512). Siz bir resim dosyasını Unity’e hangi formatta atarsanız atın bu resim dosyası Unity içerisinde otomatik olarak bir texture‘a dönüştürülür. Bu esnada bu resim dosyanın kaç MB olduğunun texture’un boyutuna bir etkisi olmazken, resim dosyasının kalitesi, Unity tarafından oluşturulan texture’un kalitesine direkt olarak etki eder. Siz Unity’e 512×512’lik bir resim dosyası atsanız bile, bunu Inspector‘da en altta yer alan “Max Size“dan otomatik olarak 256×256’lık bir texture’a da dönüştürebilirsiniz.
  • Mobil platformlarda Player Settings‘teki Resolution Scaling Mode‘u Fixed DPI yapın ve Target DPI değerini de 320 yapın. Bu ayar, oyunun maksimum 320dpi çözünürlüğünde ekrana çizilmesini sağlar. Özellikle retina ekranlar ufak olmalarına rağmen bilgisayar çözünürlüklerinin bile üzerinde çözünürlüklere sahip olabildikleri için, bu ekranlarda render almak çok zaman alabilir. Ancak oyunu maksimum 320dpi’da render alarak bu süreci epey hızlandırabilirsiniz. Dilerseniz 320dpi değerini, oyunun görselliğinde büyük bir fark olmadığı sürece daha da düşürmeyi deneyebilirsiniz.
  • Minimum sayıda materyal kullanın. Oyun esnasında kameranın görüş alanındaki farklı materyal sayısı ne kadar çok olursa, “SetPass calls” dediğimiz değer de o kadar çok olur; bu da oyunun yavaşlamasına sebep olur.
  • [16] Shader’larınızı olabildiğince basit tutun/seçin. Yeni oluşturulan materyallere otomatik olarak atanan Standard Shader‘ı minimum düzeyde kullanın (mobil oyunlarda hiç kullanmamaya çalışın). Bu shader, PBR adı verilen gerçekçi ışıklandırmaya yönelik hesaplamalar üzerine kurulduğu için, özellikle mobil platformlarda çok fazla performans harcar. Materyaliniz için bir shader seçerken öncelikle Mobile altında listelenmiş shader’lara bakın, burada ihtiyacınızı karşılayan bir shader yoksa o zaman Legacy Shaders‘a yönelin.
  • Dinamik (gerçek zamanlı) gölgelendirmeden mümkün olduğunca az faydalanın. Eğer sahnenizdeki sabit duran (static) objeler bile dinamik gölge düşürüyorsa burada performans açısından büyük bir sıkıntı söz konusu. Sahnenizdeki sabit objeleri (ev, duvar, masa vb. yeri kesinlikle hiç değişmeyecek ve oyun sırasında kesinlikle yok olmayacak objeler) Inspector‘dan “Static” yapın ve onlara lightmapping uygulayın. Hem çok daha kaliteli gölgeler elde edersiniz hem de oyunu çalıştıran cihaz derin bir “oh” çeker.
  • Game panelinin sağ üstündeki Stats kapalıysa açın ve oyun boyunca bir gözünüzü “Batches” ve “SetPass calls“ta tutun. Bu değerler ne kadar az olursa o kadar iyi. “SetPass calls” değeri, kameranın görüş alanındaki objelerde ne kadar çok farklı materyal varsa o kadar artar. “Batches” değeri ise, kameranın görüş alanındaki objelerin sayısı ile doğru orantılı olarak artar. Gölgelerin açık olduğu durumda Batches değeri yaklaşık ikiye katlanabilir. Bu iki değerin optimizasyonunda önceliği “SetPass calls”a vermelisiniz çünkü bu değerin yüksek olması, “Batches”ın yüksek olmasına göre performansa daha çok etki eder. “SetPass calls” değerini düşürmenin tek yolu var: olabildiğince az materyal kullanmak; yani olabildiğince çok objeye aynı materyali vermelisiniz.
  • (Özellikle mobil) [15] Eğer sahnenizde aynı materyali taşıyan ve low-poly olan çok fazla obje varsa, Player Settings‘teki Dynamic Batching‘i açarak “Batches“ın azalmasına yardımcı olabilirsiniz. Bu ayar, Unity’nin kameranın görüş alanındaki aynı materyale sahip objeleri GPU’ya yollamadan önce birleştirerek tek bir obje yapmasını sağlar, böylece birden çok obje tek bir seferde çizilir. Bir frame’de kaç objenin birleştirildiğini, Stats ekranında “Saved by batching” olarak görebilirsiniz.
  • Eğer oyununuz kapalı bir alanda veya bir şehirde geçiyorsa, Occlusion Culling kullanarak bir duvarın veya binanın arkasında kalan objelerin boş yere GPU’ya yollanmasını engelleyin. Occlusion Culling dersim için: https://yasirkula.com/2020/03/31/unity-occlusion-culling-sistemi/
  • Farklı texture’lara sahip objelerin aynı materyali kullanmaları için “texture atlasing” tekniğinden faydalanın. Yalnız bunun için 3D modelleme konusunda biraz bilginizin olması ya da Asset Store’daki plugin’lerden birine sahip olmanız lazım.
  • Çoğu 3D modelleme uygulaması, modeli “vertex color” ile boyamanıza imkan verir. Eğer ki modeliniz sadece düz renklerden oluşuyorsa, ona texture vermek yerine sadece vertex color uygulayabilirsiniz. Bu durumda objenin Unity’deki shader’ını, vertex color destekleyen bir shader ile değiştirmeyi unutmayın (bunun için “unity vertex color diffuse shader” gibi arama yapabilirsiniz).
  • [1] 3D modellerinizde, oyuncunun hiç göremeyeceği yüzleri (triangle) silin. Örneğin bardak modelinizin alt yüzü oyun esnasında hiç gözükmüyorsa, oradaki triangle’ları silin gitsin.
  • [1][16] Özellikle mobilde Image Effect (post processing) kullanımını olabildiğince minimum düzeyde tutun. Bu efektler sandığınızdan daha fazla performans harcayabilir.
  • Fog (sis) kullanmak da performans için iyi değilmiş. Eğer fog, oyununuzda çok ciddi bir yere sahip değilse kullanmaktan kaçının.
  • Bump mapping (normal mapping), bir objeye düşen ışığın çok daha gerçekçi durmasına yardımcı olabiliyor ancak tahmin edebileceğiniz üzere bunu yaparken de ekstra performans yiyor. Eğer kullanacaksanız sadece çok kritik objeler için normal mapping kullanın (mesela player’ın elinde tuttuğu silah objesi için). Bu objelerin normal map texture’larının Max Size‘ını, objenin görünümüne büyük bir etki etmeyecek şekilde olabildiğince düşürmeye çalışın.
  • [1] Unity’nin son sürümleri ile gelen dinamik skybox yerine, cubemap kullanılarak hazırlanmış eski usül skybox’lar kullanın. Yeni skybox dinamik olarak oluşturulduğu için performans açısından daha kötüdür.
  • Eğer ki oyununuzda skybox (gökyüzü) kullanmıyorsanız şu ayarları yapın:

Main Camera-“Clear Flags“: “Solid Color”
Window-Lighting-Skybox: “None”
Window-Lighting-Ambient Source: “Color”
Window-Lighting-Reflection Source: “Custom” (reflective shader kullanmıyorsanız Cubemap‘i “None” olarak bırakın)

  • Window-Lighting‘de “Precomputed Realtime GI” ve “Baked GI” adında 2 seçenek bulunmaktadır. Bu seçenekler lightmapping ile alakalıdırlar. Aralarındaki fark ise şöyle ki: “Precomputed Realtime GI”da ortamdaki ışık sayısı, ışıkların rengi veya şiddeti dinamik olarak değişse bile sahnedeki ışıklandırma ona göre otomatik olarak güncellenirken, “Baked GI”da ortamdaki ışıkların durumu değişse de lightmap sabit kalır.

Çoğu oyunda sahnedeki ışıklar sabit olduğu için “Baked GI” doğru tercihtir. İlaveten, “Baked GI” ile oluşturulan lightmap görsel olarak daha kaliteli olur. Yani dikkat etmeniz gereken noktalar şunlar:

– oyunda lightmapping kullanmayacak mısınız (çoğu basit oyunda kullanmazsınız)? O halde hem “Precomputed Realtime GI”ı hem de “Baked GI”ı kapatın
– lightmapping kullanacaksınız ve sahnedeki ışıklandırma dinamik olarak değişecek mi? O zaman “Precomputed Realtime GI”ı açık bırakırken “Baked GI”ı kapatın
– lightmapping kullanacaksınız ve sahnedeki ışıklandırma sabit mi kalacak? O zaman “Baked GI”ı açık bırakırken “Precomputed Realtime GI”ı kapatın
– lightmapping kullanacaksınız ve oyunu mobil platformlara mı tasarlıyorsunuz? O zaman çok zorunda kalmadığınız sürece “Precomputed Realtime GI” kullanmayın

  • [1] Lightmapping kullanıyorsanız, çok ufak statik objeleri bu sürece dahil etmeyin çünkü obje çok ufak olduğu için lightmapping’in etkisi belki de belli bile olmayacak, ama obje boş yere lightmap texture’larınızın boyutunu artıracak. Onun yerine bu objeleri light probe‘lar ile ışıklandırın (illa ışıklandıracaksanız). Bir statik objeyi lightmapping’den çıkarmak için, objenin Inspector’undaki Static işaretinin yanında yer alan oka tıklayın ve Lightmap Static‘i kapatın (yeni Unity sürümlerinde Contribute GI).
  • (Android) Player Settings-Resolution and Presentation‘daki “Use 32-bit Display Buffer” seçeneğini kapatarak oyununuzu mobil cihazınızda test edin. Eğer grafiklerde göze batan bir değişiklik olmadıysa bu seçeneği kapalı bırakın.
  • (Özellikle Android) Eğer ki özellikle OpenGLES3 gerektiren bir shader kullanmadığınızı düşünüyorsanız Player Settings-Other Settings‘teki “Auto Graphics API” seçeneğini kapatın ve “Graphics APIs” listesinden OpenGLES3’ü kaldırıp listede sadece OpenGLES2‘yi bırakın ve oyunu test edin. Eğer göze batan bir değişiklik olmadıysa bu ayarı böyle bırakın. Forumlarda, bu değişikliğin performansa çok etki ettiğini söyleyenler var.
  • (Özellikle mobil) Player Settings‘teki Color Space‘i Gamma‘da tutmaya çalışın. Alternatif seçenek olan Linear daha gerçekçi ışıklandırma sağlar ama daha yavaştır.
  • [1] Player Settings‘te Multithreaded Rendering açık değilse açın.
  • Bazı insanlar Unity’de Vsync‘i açınca oyunlarının daha hızlı hale geldiğini iddia ediyorlar. Bunu kesin olarak bilmiyorum ancak denemek isterseniz Edit-Project Settings-Quality yolunu izleyip, build aldığınız platformun varsayılan grafik düzeyine geçiş yapın (varsayılan düzey yeşil bir tik ile gösterilir). Ardından “V Sync Count“u “Every V Blank” yapın ve oyunu test edin.
  • Gerçek zamanlı (dinamik) gölgeler çok fazla performans harcar ve bunları olabildiğince optimize etmek veya sayılarını azaltmak büyük önem arz eder. Unity’de “Hard Shadows” ve “Soft Shadows” olmak üzere iki farklı gölgelendirme yöntemi mevcut. “Hard Shadows” daha kaba durur ancak daha az performans harcar. Bu yüzden, illa dinamik gölge kullanacaksanız, görsel olarak çok çok büyük bir fark olmadığı sürece ışıklarınızda “Hard Shadows” kullanın.
  • Eğer gölge kullanıyorsanız, Edit-Project Settings-Quality‘den Shadow Distance‘ı olabildiğince düşürün. Bu değer, gölgelerin çizileceği en uzak mesafeyi belirler. Daha uzaktaki objelerin gölgeleri çizilmez. Quality ayarlarındaki Shadow Cascades‘i de, görsele çok büyük etkisi olmadığı sürece No Cascades yapın.
  • [4][14] Eğer sahnenizde Light Probe veya Reflection Probe kullanmıyorsanız, objelerinizin Mesh Renderer‘larının Light Probes ve Reflection Probes değerlerini Off yapın. İlaveten “Motion Vectors” değerini de “Force No Motion” yapın. Mümkün olduğunca Cast Shadows (objenin gölge bırakmasını sağlar) ve Receive Shadows (diğer objelerin gölgelerinin bu objenin üzerine düşmesini sağlar) değerlerini kapatın. Örneğin çok ufak objelerin veya gölgesi belli bile olmayan objelerin gölge hesaplamaları yüzünden boş yere performansınızı düşürmelerine izin vermeyin.
  • [8][14] 3D model asset’lerinizin Inspector’daki Optimize Mesh değerinin açık, Read/Write Enabled değerinin kapalı olduğundan emin olun. Eğer objeye Mesh Collider verecekseniz o zaman “Read/Write Enabled”ı açık bırakmak isteyebilirsiniz.
  • [15] Bir 3D modeli hiçbir zaman normal map ile kullanmıyorsanız, Inspector’daki Tangents‘ı None yapın. Eğer modeli Unlit materyal harici bir materyalle kullanmıyorsanız, o zaman Normals‘ı da None yapın. Böylece model, kullanmadığınız bu verilerden arınmış olacak.
  • [8] Animasyona sahip 3D modellerinizin Inspector’daki Rig sekmesinde yer alan Animation Type değeri Generic veya Humanoid ise, aynı sekmedeki Optimize Game Objects‘i etkinleştirin. Bu şekilde 3D modelin child objeleri Unity tarafından optimize edilip Hierarchy’den gizlenir. Eğer belli başlı child objelerin Hierarchy’den gizlenmesini istemiyorsanız, onları Extra Transforms To Expose listesine ekleyebilirsiniz. Eğer ki “Optimize Game Objects” seçeneğini değiştirdiğiniz bir 3D model halihazırda bir prefab’ın parçası ise, 3D modeli o prefab’dan silip tekrar eklemeniz gerekebilir çünkü child objelerin Hierarchy’den gizlenmesi değişikliği prefab’lara otomatik olarak uygulanmaz.
  • [9][14] Animasyona sahip olmayan 3D modellerinizin Rig sekmesindeki Animation Type‘ını None yapın. Animasyona sahip modellerde ise, inverse kinematics (IK) veya animation retargeting kullanmıyorsanız, Humanoid yerine Legacy (tercihen) veya Generic Animation Type kullanın.
  • [16] LOD (Level of Detail) sistemini kullanarak, uzaktaki objelerin daha basit geometriler ile çizilmesini sağlayın. Bu optimizasyon, özellikle büyük çaplı PC oyunlarında çok önemlidir. Daha fazla bilgi için: https://docs.unity3d.com/Manual/LevelOfDetail.html
  • 2D bir oyun yapıyorsanız, UI Optimizasyonu kısmında bahsettiğim Sprite Atlas tekniği 2D Sprite’larınızda da işe yarar. Bu sefer oluşturacağınız Sprite Atlas asset’lerinizin Allow Rotation ve Tight Packing değerlerini açık bırakabilirsiniz, bunlar UI dışında bir sıkıntı çıkarmazlar.
  • [15] Belki çok basit kaçacak ama, Edit-Project Settings-Quality‘den oyunun grafik ayarını düşürmek (build alınan platformun Default grafik ayarını değiştirmek) belki de oyununuz için fazlasıyla yeterli olabilir. Default grafik ayarı, yeşil bir tik ile gösterilir.
  • Oyununuzun görseline büyük etkisi olmadığı sürece kameranızın Allow HDR ve Allow MSAA değerlerini kapatın. HDR genelde bloom vari post-processing efektlerde kullanılırken, MSAA ise anti-aliasing’i aktifleştirir. Eğer illa anti-aliasing kullanacaksanız, Edit-Project Settings-Quality‘de Anti Aliasing‘in açık olduğundan emin olun.
  • (Android) Eğer anti-aliasing kullanmıyorsanız, Player Settings‘teki Blit Type‘ı Auto yapmayı deneyin (sadece 2018.3 ve üstü için).
  • Texture‘larda size-of-2 diye bir kural var. Bilgisayarlar en temelinde binary (0 ve 1) üzerine kurulu olduğundan dolayı her türlü işlem aslında 2 tabanında gerçekleşir. Belki de bu yüzdendir ki oyun motorları, texture ebatları olarak hep 2’nin katlarını tercih eder (16, 32, 64, 128, 256…). Eğer ki Unity’e 2’nin katlarından oluşmayan ebatlarda bir texture verirseniz, Unity bu texture’u kendisi 2’nin bir katına çevirir. Yani isteseniz de istemeseniz de aslında size-of-2 texture’lar kullanıyorsunuz (bu durum sprite’lar için geçerli değil). Bu yüzden texture oluştururken kendiniz size-of-2 kuralına uymaya çalışın. Bu şekilde texture’u Unity’de efektif bir şekilde compress edebilme (sıkıştırma) şansına da sahip olacaksınız. Yok illa ki 75×75 texture kullanacağım diyorsanız da 128-75=53 piksellik boş alanı başka bir texture için kullanmayı düşünebilirsiniz (texture atlasing).
  • Kameranızın Far Clipping Plane değerini olabildiğince azaltın. Bu değer, kameranızın ekrana çizdireceği objelerin kameradan maksimum ne kadar uzakta olabileceğini belirler. Daha uzaktaki objeler ekrana çizilmez. Bu optimizasyon aynı zamanda z-fighting denilen sıkıntıyı gidermeye de yardımcı olur.
  • [1] Camera‘nın çok bilinmeyen bir değişkeni bulunmaktadır: layerCullDistances. Bu değişken vasıtasıyla, kameranın Far Clipping Plane‘inin her layer için farklı olmasını sağlayabilirsiniz. Bu şekilde, sahnenizdeki ufak objeleri farklı bir layer’a alıp bu layer’ın ekrana çizilme mesafesini ufak bir değer yaparsanız, diğer objelerin aksine bu objeler görüş alanından daha çabuk kaybolurlar (ekrana çizilmeyen her obje artı performans demektir), ama zaten ufak oldukları için oyuncu bunu anlamaz bile. Daha fazla bilgi için: https://docs.unity3d.com/ScriptReference/Camera-layerCullDistances.html
  • Eğer oyununuzdaki sahnelerde genel olarak hep aynı shader’ları kullanıyorsanız, Player Settings‘teki Keep Loaded Shaders Alive seçeneğini işaretleyerek, sahneler arası geçişlerde shader’ların hafızadan silinmesini engelleyebilirsiniz. Bu şekilde, her yeni sahne açılışında aynı shader’lar tekrar tekrar hafızadan silinip hemen ardından tekrar hafızaya alınmakla uğraşılmaz, shader’lar hep hafızada kalırlar.

Oyun Boyutu (Build) Optimizasyonu

NOT: Unity 2018.2‘den itibaren, şu plugin’i kullanarak oyununuzda en çok hangi asset’lerin yer kapladığını görebilirsiniz: https://assetstore.unity.com/packages/tools/utilities/build-report-inspector-for-unity-119923

  • Oyunlarda en çok boyutu genellikle texture‘lar ve müzik dosyaları kaplar. Daha ufak bir build için bu dosyaların Inspector’daki compression ayarlarında olabildiğince ince ayar yapın.
  • Resources veya StreamingAssets klasörlerine koyduğunuz asset’ler, hiç kullanılmasalar bile oyuna dahil olurlar ve oyunun boyutuna olumsuz etki ederler. Bu yüzden bu klasörlerde kullanmadığınız asset’ler bırakmayın. Eğer projenize import ettiğiniz bir plugin’in içerisinde Resources klasörü varsa, klasörün içindeki asset’lerin plugin’in çalışması için gerekli olup olmadığını kontrol edin. Bazen plugin’in demo sahnesinde kullanılan asset’ler Resources klasöründe yer alır ve siz farkında olmadan oyununuza dahil olurlar.
  • Alpha kanalını kullanmadığınız texture’ların Inspector’daki Alpha Source‘unu None yapın. Bu şekilde bazı texture’ların boyutu yarıya kadar inebilir (zaten alpha’sı olmayan texture’lara bir etkisi olmaz). Alpha kanalı genelde saydamlık için kullanılır, bu yüzden sadece tamamen opak objelerin texture’ları bu optimizasyondan faydalanabilir. Nadiren de olsa alpha kanalı, Standard shader’da objenin ışıklandırmasına etki etmek için kullanılabilir. Bu yüzden bu optimizasyonu uygularken, sahnenizde objenin bir klonu olsun ve optimizasyonun objeye görsel olarak bir etkisi olmadığından emin olun.
  • [4][14] 3D modellerinizin Mesh Compression‘ına Off harici bir değer vermeyi deneyin. Eğer 3D modelin görüntüsünde gözle görülür bir bozulma olursa bu ayarı geri alın.
  • [14] Player Settings‘teki Vertex Compression, 3D modellerinizi sıkıştırma konusunda yardımcı olur. Burada Position harici her şeyi işaretlemek isteyebilirsiniz. Benzer şekilde, Optimize Mesh Data‘yı açarak, 3D modelde kullanılmayan UV kanalları vs. varsa bunların build esnasında modelden silinmesini sağlayabilirsiniz. Hangi kanalların kullanıldığını hesaplamak için, Unity modelin materyaline atanmış shader’ı inceler.
  • (Android) Unity ile x86 işlemcili Android cihazlara da build almak mümkündür. Bu her ne kadar kulağa güzel gelse de, oyunun x86 işlemci versiyonunu APK’ya eklemek, dosya boyutunu ciddi miktarda artırabilir. x86 işlemciyi APK’nıza dahil etmemek için, Player Settings-Other Settings‘teki Device Filter‘ın değerini ARMv7 yapabilirsiniz. Zaten artık Google Play’e x86’lı bir APK’yı upload edemediğiniz için (Unity x64 desteklemediğinden), x86’yı APK’nıza dahil etmeniz için bir sebep kalmıyor. Unity’nin ilerleyen sürümleriyle birlikte x86 desteğinin tamamen kesileceğinin de buradan altını çizelim.
  • (Mobil platformlar) Oyununuzu build alırken, kodunuzda kullandığınız class’ları içeren dll‘ler de apk dosyasına eklenir ancak bu dll’ler ile gelen kullanmadığınız diğer class’lar boş yere fazlalık oluşturur. Unity’nin build alırken bu class’ları otomatik olarak yok saymasını sağlayan bir özellik var: “Stripping Level” (Player Settings-Other Settings)(yeni Unity sürümlerinde “Managed Stripping Level“). Bu seçeneği “Strip Assemblies” yaparsanız dll dosyalarındaki fazlalıklar atılır (dokümantasyonda yazdığına göre Reflection kullanıyorsanız bu bir sıkıntı oluşturabilirmiş). Eğer bu seçeneği “Use micro mscorlib” yaparsanız “Strip Assemblies”e ilaveten bir de .NET kütüphanesinde de bir optimizasyon söz konusu olur (benim kullandığım seçenek bu). Bu saydığım seçenekler yeni Unity sürümlerinde Low, Medium ve High olarak geçmekte. Ben bu seçeneği High yapıyorum ve script’lerle alakalı bir sorun yaşamadığım sürece de High’da bırakıyorum.
  • Window-Package Manager‘ı açıp All packages‘ı Built-in packages yapın ve oradaki ihtiyacınız olmayan modülleri Disable edin. Örneğin çoğu oyun AI (NavMesh), Asset Bundle veya Cloth modüllerini kullanmaz. Bazen kullandığınız bir plugin, kapattığınız bir modülü kullanıyor olabilir. Neyse ki bu durumda konsola bir hata mesajı düşer ve hangi modülü tekrar Enable ederek sorunu çözebileceğiniz yazar.
  • [1] Edit-Project Settings-Graphics‘te “Built-in Shader Settings” başlığı altında yer alan kullanmadığınız grafik özelliklerinin değerini “Built-in shader“dan “No Support“a çekin. Örneğin çoğu mobil oyunda Deferred, Motion Vectors, Light Halo ve Lens Flare kullanılmaz. Benzer şekilde, Always Included Shaders‘ta bariz kullanmadığınız shader’lar varsa bunları kaldırın; örneğin Video Player kullanmıyorsanız Video shader’larını ve UI sistemini kullanmıyorsanız UI shader’larını kaldırabilirsiniz.

Diğer Optimizasyonlar

  • [3] Sırf Hierarchy düzenli dursun diye objelerinizi Empty GameObject‘lerin child’ı yapmayın. Bir child objenin Transform’u değiştiğinde, Unity kendi içinde bu Transform’un tüm kardeşlerini potansiyel olarak değişmiş işaretler ve bu işlem CPU’dan yer. Bunun için sahnenizdeki hareketli objeler Hierarchy’nin root’unda dursun, yani bir parent’ları olmasın. Ama bir Empty GameObject’in içindeki tüm objeler hareketsizse, o Empty GameObject o şekilde kalabilir, bir sıkıntı yok.
  • [5][9][16] Çok basit animasyonlar için Animator component’i yerine Animation component’ini kullanın veya DOTween gibi kod bazlı bir animasyon plugin’i kullanın.
  • Çok kısa ve sıklıkla kullandığınız AudioClip‘lerin Load Type‘ını Decompress On Load yapın. Bu AudioClip’ler hafızada çok daha fazla yer kaplar ama oyun esnasında decompression için CPU harcamazlar. Bu yüzden de bu ayar sadece çok kısa ses efektleri için idealdir. Müziklerde ise Streaming seçeneğini seçin. Bu ayar seçili olduğunda müzik diskten okunur ve müziğin tamamı hafızaya alınmaz, sadece çalmakta olan kısmı hafızaya alınır.
  • [14][16] 3D uzayda çalan AudioClip‘lerin Force To Mono seçeneğini açın; aksi taktirde bu sesler oyun esnasında dinamik olarak mono’ya çevrilirler ve bu esnada CPU harcarlar. Aslında müzik gibi, 3D uzaydan bağımsız olarak çalan ses dosyalarını da Force To Mono yapmak isteyebilirsiniz çünkü AudioSource component’i, Mono sesleri daha kolay çalar.
  • AudioClip‘lerle ilgili diğer optimizasyonlar için: https://yasirkula.com/2020/03/31/unity-audioclip-import-ayarlari/
  • [3] Oyununuzda aynı anda pek çok AudioSource oynuyorsa, Edit-Project Settings-Audio‘daki Max Virtual Voices ve Max Real Voices‘ın değerlerini olabildiğince kısarak, Unity’nin tek bir frame’de hesaba kattığı AudioSource sayısına bir limit koyabilirsiniz.
  • Oyun boyunca sürekli kullandığınız asset’leri, Player Settings‘teki Preloaded Assets listesine ekleyerek, asset’in oyunun başında hafızaya alınıp oyun bitene kadar hafızadan çıkmamasını sağlayabilirsiniz. Bu sayede sahneler arası geçişlerde bile asset hafızada kalır ve oyun esnasında asset’i yüklerken herhangi bir gecikme yaşanmaz.

Yararlanılan Kaynaklar

[1] https://learn.unity.com/tutorial/optimizing-graphics-in-unity
[2] https://learn.unity.com/tutorial/optimizing-unity-ui
[3] Unite Berlin 2018 – Unity’s Evolving Best Practices: https://www.youtube.com/watch?v=W45-fsnPhJY
[4] https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity.html
[5] Unite Europe 2017 – Squeezing Unity: Tips for raising performance: https://www.youtube.com/watch?v=_wxitgdx-UI
[6] https://www.habrador.com/tutorials/unity-optimization/
[7] https://makaka.org/unity-tutorials/optimization
[8] Unite 2016 – Let’s Talk (Content) Optimization: https://www.youtube.com/watch?v=n-oZa4Fb12U
[9] https://unity3d.com/how-to/unity-best-practices-for-engine-performance
[10] Unite 2012 – Performance Optimization Tips and Tricks for Unity: https://www.youtube.com/watch?v=jZ4LL1LlqF8
[11] https://unity3d.com/how-to/work-optimally-with-unity-apis
[12] https://unity3d.com/how-to/unity-common-mistakes-to-avoid
[13] https://unity3d.com/how-to/unity-ui-optimization-tips
[14] https://learn.unity.com/tutorial/mobile-optimization
[15] https://learn.unity.com/tutorial/fixing-performance-problems
[16] https://create.unity3d.com/nine-ways-to-optimize-game-development

yorum
  1. Burak Cem Dursun dedi ki:

    Hocam bunları videolarla anlatacak, video serileri oluşturacak mısınız?

  2. Furkan Katı dedi ki:

    Hocam ben unity de oyunu yapıyorum profiler dan baktığımda bilgisayarda 120 fps civarı alıyorum. Fakat mobile geçince, yani apk yapıp telefona atınca genelde oyun yavaş oluyor. Oyunun mobilde de hızlı çalışacağından nasıl emin olabiliriz ? Örneğin bilgisayarda kaç fps almam lazım ? Bu şekilde bir kıyaslama mümkün mü yoksa her seferinde mobilde denemem gerekir mi ?

  3. eniskun dedi ki:

    Merhaba hocam, InvokeRepeating mi yoksa StartCoroutine kullanmak optimizasyon açısından daha verimlidir?

    • yasirkula dedi ki:

      InvokeRepeating reflection kullandığı için, ben StartCoroutine’i tercih ediyorum. Ancak Unity arkaplanda InvokeRepeating’in kendini tekrar etmesini coroutine’den daha optimize bir şekilde mi sağlıyor bilmediğimden, oyun esnasındaki verimlerini kıyaslayamayacağım. 1,000,000 adet InvokeRepeating ve StartCoroutine’i ayrı ayrı çalıştırarak oyunun FPS’ini hangisinin daha çok düşürdüğünü kontrol edebilirsiniz. Sonucu burada da paylaşırsanız sevinirim.

      • eniskun dedi ki:

        Küçük çaplı bir test yaptım.Editörde InvokeRepeating daha iyi sonuç verdi ama build edince ikiside neredeyse aynı sonucu verdi.İnternetten araştırdığım kadarıyla büyük çoğunluk StartCoroutine kullanmanın daha mantıklı olduğunu söylemiş

  4. Bulent Levent dedi ki:

    Yasir bey iyi akşamlar

    Benim oyunumdaki objelerde yuzeyiin altına gitmemesi için collider ekledim . objelerde rigidbody yerçekimi açık . Bu objeler enemy . Birbirlerine çarpınca yönleri ve açıları değişiyor .rigidbody de freeze positionu ve rotationa tik işaretli olduğunda yön değişmiyor fakat objeler yokuş veya benzer bir yuzeyde bir kısmı havada kalıyor . Colliderı olan enemy objelerinin birbirinden etkilenmemesi için ne yapabilirim .

  5. selim dedi ki:

    Hocam merhaba;
    2d unity de Oyunda yukardan aşağıya sarkan bir zincir ve alt ucun da bir top var ve birbirine sabitledim, bu zincir yukardan sabit olacak şekil de topla sağa ve sola devamlı hareket ettirmek istiyorum fakat kod la meydana getiremedim, yardımcı olabilirseniz sevinirim.

    • yasirkula dedi ki:

      Zincir ve top birbirine bağlıyken fiziksel hareketleri gerçekçi olsun istiyorsanız, Joint 2D component’leri kullanmanız gerekebilir. Ardından koda 2 saniye boyunca sağ yönde, 2 saniye boyunca sol yönde güç uygulamayı deneyebilirsiniz (bu döngüyü sürekli tekrar edin).

    • selim dedi ki:

      Hocam merhaba; aslında yukarda ki video da gösterilen sarkaç gibi unity 2d de aynısını kod yazıp ya da farklı bir şekil de uygulayabilir miyiz, bunun için kod yazmayı denedim ama çalışmadı, bununla ilgili video falan var mıdır, yardımcı olabilirseniz sevinirim.

      • yasirkula dedi ki:

        Kod yazmadan, Animation veya Animator component’i ile bunu halledebilirsiniz. Bilginiz yoksa “unity animation ders” veya “unity animation tutorial” şeklinde arama yapabilirsiniz.

  6. Mehmet dedi ki:

    Yasir Bey Merhaba,
    Bir oyun yapıyorum(endless race tarzı), başlangıçta her şey çok iyi, stats’ta fps 125-150 ile başlıyor ama bir süre sonra aniden 20 lere düşüyor. Neredeyse yapmadığım optimizasyon kalmadı.
    Coroutinleri kaldırdım,
    fixedUpdateleri kaldırdım, lateupdateleri kullandım gerekli yerlerde,
    physics hesaplarını minimuma çektim,
    etraftaki objeleri azalttım,
    instantiate yerine objectpooling kullandım,
    mp3 ayarını streaming + quality %70 yaptım,
    find object veya start ya da awake harici getcompenent hiç kullanmadım, (bir yer hariç, o da mecburi)
    daha aklıma gelmeyen bir çok şey, bunlar sadece düşüşün biraz daha geç başlamasını sağladı.

    profiler görselleri de şu şekilde;

    https://i.hizliresim.com/b9QnRV.jpg

    https://i.hizliresim.com/zyQ6qD.jpg

    https://i.hizliresim.com/OaJG4Z.jpg

    önerebileceğiniz yöntemler var mıdır acaba?

    Bir de şu EditorLoop nedir acaba, araştırdım fakat bir şey anlamadım.

    Şimdiden teşekkür ederim, kolay gelsin.

    • yasirkula dedi ki:

      EditorLoop, Unity editörünün Profiler için veya başka bir şeyler için harcadığı süreyi temsil ediyor. Niye o kadar çok süre harcıyor ben de anlayamadım ama eğer EditorLoop’suz Profiler kullanmak istiyorsanız, oyunu “Development Mode” ve “Autoconnect Profiler” seçenekleri açıkken Build&Run yapabilirsiniz. Bu şekilde, build aldığınız oyunu profile etmiş olacaksınız. EditorLoop’un içini görmek istiyorsanız da, yukarıdaki “Profile Editor” seçeneğini açabilirsiniz.

  7. Bulent Levent dedi ki:

    Yasir bey profilden baktım ençok ( %44 ) FixedUpdate.PhysicsFixedUpdate bu kasıyor .Fakat kodlarımda fixedUpdate yok . sabit ojelerdeki rigidbody kaldırdım .Unity 3d sürümüm 2017 neden bu kadar kasıyor . Nasıl fps artışı sağlarım .

    • yasirkula dedi ki:

      Eğer Profiler Timeline görünümündeyse onu Hierarchy görünümüne alıp listeyi “Time ms”ye göre sıraladıktan sonra, en çok ms nerede harcanıyor bulmaya çalışın. Örneğin FixedUpdate satırının solunda bir ok varsa, o oka tıklayarak satırın içine girin ve bu şekilde nokta atışı yapmaya çalışın.

  8. Bulent Levent dedi ki:

    Yasir bey 35 mb çıktısı olan andoid oyunum 15 fps nin uzerine çıkmıyor unity 3d sorunlu bir motormu oyunu yaptıktan sonra optimizasyonla çok uğraşmak gerekiyor hiç bir yöntem fayda etmedi . kullandıpım texler bile 256×256 .

  9. Bulent Levent dedi ki:

    Yasir bey sizin yazdıınız optimizasyonları uyguladım ayarları değiştirdim .Unity nin game ekranında oyun sorunsuz oynanıyor fakat telefonda canvas ve partikul dışında hiçbirşey görenmuyor . Nasıl duzeltebilirim .

    • Bulent Levent dedi ki:

      Yasir bey
      Disable Depth and Stencil ın tikini kaldırınca duzeldi . Bu problemi yaşayan arkadaşlara yardımcı olmak ve sizi meşgul etmemek için yazıyorum .Bilgileriniz ve yardımlarınız için teşekkurler .

  10. Ömer Faruk Kulaoğlu dedi ki:

    Selamlar Yasir Bey iki sorum olacaktı.Bir uygulama yapıyorum 2D olarak projeye başladım ama uygulama içinde asla hareket etmeyen 3D objeler kullandım (70 adet low poly ağaç) ve kamerada hareket etmiyor sadece belli bir açıda obje olarak uygulamada duruyorlar.Bu projeyi 2D değilde 3D olarak oluşturmamın bana bir faydası olur mu daha iyi performans alırmıyım ? (Uygulamam genelinde fazlaca UI elemanlarından oluşan 2D bazlı bir uygulama.)

    Uygulamamızı 2D veya 3D proje olarak oluşturmak arasındaki farklar nelerdir ve Unity neden böyle bir seçenek sunuyor çünkü deneyimlerime göre 2D bir projedede üç boyuları objeleri kullanabiliyoruz farkları nelerdir ?

    • yasirkula dedi ki:

      Performans konusunda emin olamadım. Eğer tüm ağaçlar aynı açıya sahipse, ağaçların 2D sprite olması daha iyi olabilir. Ağaçların eğimleri farklıysa, her bir eğim için ayrı bir sprite gerekeceğinden bu sefer 3D daha iyi olabilir. Tabi bir de overdraw konusu var: 2D oyunda sprite’lar üst üste çizildiği için overdraw artıp performansı olumsuz etkiler, 3D modeller depth buffer sayesinde overdraw’dan daha az etkilenirler. Ama en nihayetinde 3D veya 2D hangisi daha iyi olur sanırım test etmeden bilemeyeceksiniz.

      2D oyunda 2 boyutlu görselleri Sprite Renderer kullanarak ekranda gösterebilirsiniz, 3D modele gerek yok.

  11. burak dedi ki:

    hocam merhaba unityde yeni bir material oluşturdum shader değiştirmek istiyorm ama hiç bir şekilde tıklanmıyor shader kısmı kapalı durumda problem nedir nasıl çözebilirm ? 5.6.7f1 versiyonu kullanıyorum bilgisayarım 32bit olduğu için.

    • yasirkula dedi ki:

      Eğer sahnedeki bir obje seçili ise ve objenin materyali varsayılan materyal ise, o materyalin herhangi bir değerini değiştiremezsiniz. Project panelinden materyal asset’inizi seçtiğinize emin misiniz?

  12. Bulent Levent dedi ki:

    Yasir bey yaptığım ( Android ) oyunda setpass Calls 1500 e kadar çıkıyor . Oyundaki karakter hariç tum objelerin gölgesini kapattım ( Shodow casters 8 ) şu an oyun taslak .(Telefonuma oyunu attım çok kasıyor karakter nerede ise hareket etmiyor .) Sadece haritayı yaptım ve objeleri yaptım.( Yol kopru bina vs. ) Harita buyuk , objelerin yarısını bile eklemedim .Hiç bir kod yazmadım (Sadece karakter hareket kodu var.)
    Nasıl optimize edebilirim . Bu şekilde unity ile çok kuçuk oyunlar yapılabilir . Acaba boşuna mı uğraşıyoruz .
    Bu işte çok acemiyim kesin bilmediğim birşeyler vardır .(Oyunun yavaşlamaması için gölgelerin kapatılması grafiğin duşurulmesi çok saçma Sonuçta gta 5 gibi oyun yapmıyoruz )
    Bilgi ve tavsiye ricası ile iyi geceler .

    • yasirkula dedi ki:

      Kameranın Far Clip Plane’ini olabildiğince düşürün. Sahnenizde birden çok ışık kullanmamaya çalışın. Point Light’larda kesinlikle gölge kullanmayın, çok zorlar. Gölge olmazsa olmaz diyorsanız sadece ana Directional Light’ınızda Hard Shadow kullanıp Edit-Project Settings-Quality’den Shadow Distance’ı olabildiğince beriye çekin (bu ayarı, Android için hangi grafik Level’inde yeşil tik yanıyorsa onda değiştirin). Mobilde terrain performanslı değil diye okumuştum, bu yüzden terrain kullanıyorsanız sıkıntı yaşayabilirsiniz. Çözüm yolu: bilmiyorum. Post Processing efektler kullanıyorsanız onları kapatın, mobilde post processing açabilmek için ya oyun çok optimize olmalı ya da çok basit bir şey olmalı. Sahnenizde büyük büyük binalar varsa ve oyun boyunca genel olarak bu binaların yakınındaysak, Occlusion Culling ile binaların arkasındaki objelerin boş yere çizilmesini önleyebilirsiniz. Ben henüz denemediğim için nasıl yapıldığını bilmiyorum. Kullandığınız materyal sayısını minimuma çekin. Bunun için texture atlasing denen yöntemden olabildiğince faydalanın. Çok poligonlu modeller kullanmayın, poligon yerine normal mapping ile detay vermeye çalışın. Kompleks modeller için LOD kullanın, böylece model uzaktayken daha az performans harcar. Texture’larınızın boyutunu Inspector’da Max Size’dan düşürebildiğiniz kadar düşürün, bazen 256×256 iken bile çok güzel gözüken bir texture boş yere 1024×1024 yer kaplayabiliyor. Materyallerinizde Standard shader kullanmayın, onun yerine Legacy Shaders-Diffuse veya Mobile-Diffuse gibi shader’lar kullanın. Standard shader mobilde kötü performansa sahip.

  13. bünyamin dedi ki:

    hocam merhabalar,
    ben mimari mobil uygulama üzerinde çalışma yapıyorum, bazı modellerimi buluttan almam gerekiyor,
    bunun için asset Bundle kullanmaya çalışıyorum ancak bir türlü beceremedim bu konuda bana yarduımcı olabilir misiniz?
    asset Bundle hakkında bilgi ve nasıl kullanıldığını anlatabilir misiniz kısa ve öz şekilde,
    çok teşekkür ederim şimdiden.

    • yasirkula dedi ki:

      Daha önceden sadece bir kere asset bundle kullandım onda da Unity’nin Asset Bundle Manager’ından faydalanmıştım: https://docs.unity3d.com/Manual/AssetBundles-Manager.html

      Asset Bundle’lar seçtiğiniz asset’leri bir arşiv formatına sıkıştırıyor ve daha sonradan bu asset bundle’ları istediğiniz konumdan indirip Unity’nin belleğine yükledikten sonra bu asset bundle’daki asset’lere erişebiliyorsunuz.

      Bu konuda güncel video dersler izlemenizi öneririm çünkü benim tecrübem güncel değil ve tam detayları hatırlamıyorum.

  14. umutercan dedi ki:

    2d android bir oyunum var hocam optimizasyonlar için hangilerini önerirsiniz?

  15. niyazi mustafa dedi ki:

    Hocam unity de Croosinput kullandığımda yeni sahne geçişinde veya restart olduğunda oyun tuşa basıyorsam ve tam o sırada elimi tuştan çektiysem bir sonra ki sahnede tuş basılı kalıyor karakter kendi kendine hareket ediyor.

    • yasirkula dedi ki:

      Yeni sahnenin başında, CrossPlatformInputManager.SetButtonUp veya CrossPlatformInputManager.SetAxisZero gibi fonksiyonlarla o takılı kalan buton veya axis’i sıfırlamayı deneyebilirsiniz.

  16. Batuhan dedi ki:

    Merhaba hocam ben bi nesneyi iki sayı arasında random olarak spawnlıyorum.Ama bu sayıların içindeki belli bir aralıkta çıkmasını istemiyorun.Şöyle yani -0.9 la 0.9 arasında -0.4 le 0.5 arasında spawnlanmasını istemiyorum.

    • yasirkula dedi ki:

      0’dan 0.9’a kadar random bir sayı oluşturup, sayı 0.5’ten küçükse sayıdan 0.9 çıkarabilir, yoksa sayıyı olduğu gibi kullanabilirsiniz.

  17. niyazi mustafa dedi ki:

    Merhaba enemy adında düşmanlarım var healt bar ve melee attack kodlarım sorunsuz çalışyor yalnız prefab veya duplicate yapmak istediğimde yani birden fazla düşman yaratmak istediğimde haliyle bir düşmanı öldürsem bile hepsi ölüyor bunu nasıl çözebilirim.

    • yasirkula dedi ki:

      Düşmanın canını nasıl azaltıyorsunuz? Düşmanlar için static bir sağlık değişkeni kullanıyorsanız öyle yapmayın, her düşmanın kendi canı olmalı.

      • niyazi mustafa dedi ki:

        Enemy üzerinde ki script bu şekilde

        public float enemyHealt, wolfSkillDamage,oneAttacakDamage;
        public Slider healtSlider;
        public CircleCollider2D enemy;

        void Update()
        {

        if (PlayerController.instance.dusmaniGoruyorum && CrossPlatformInputManager.GetButtonDown(“Fire1”))
        {
        enemyHealt = enemyHealt – oneAttacakDamage;
        healtSlider.value = enemyHealt;
        if (enemyHealt <= 0)
        {
        Destroy(gameObject);
        }
        }
        }

        statik bir değer yok.

        raycast ile düşmana yaklaşıtığımda kılıç boyu kadar yaptım ve fire tuşuna bastığımda canını azaltıyorum
        canvas yardımıyla healt bar yaptım ama bütün düşmanlar ölüyor.

      • yasirkula dedi ki:

        Tek bir PlayerController objeniz olduğu için, haliyle tek bir PlayerController.instance.dusmaniGoruyorum değişkeniniz bulunmakta. Bu değişkenin değeri true ise de, Fire1 tuşuna basınca tüm düşmanların canı azalıyor. PlayerController’a “public bool DusmaniGoruyorMuyum(Enemy dusman)” fonksiyonu ekleyip Enemy script’inizde de dusmaniGoruyorum değişkeni yerine bu fonksiyonu çağırmanızı öneririm: PlayerController.instance.DusmaniGoruyorMuyum(this)

      • niyazi mustafa dedi ki:

        player controlde boyle bir metodum var dediğniz gibi yaptım

        public bool DusmanYakinda(EnemyHealtBar enemy)
        {
        if (leftHit() || rightHit())
        {
        Debug.Log(“düşman yakında ve önümde”);
        return true;

        }
        Debug.Log(“düşman gözükmüyor yada arkamda”);
        return false;
        }

        enemy healt kısmınıda

        if (PlayerController.instance.DusmanYakinda(this) && CrossPlatformInputManager.GetButtonDown(“Fire1”))
        {
        enemyHealt = enemyHealt – oneAttacakDamage;
        healtSlider.value = enemyHealt;
        if (enemyHealt <= 0)
        {
        Destroy(this);
        }
        }
        yaptım ama olumsuz olmadı yine vurduğum zaman hepsine işliyor

      • yasirkula dedi ki:

        leftHit() ve rightHit() fonksiyonlarınızda Raycast atıp bu raycast Dusman tag’ine sahip bir objeye çarpıyor mu diye kontrol ediyorsunuz tahminimce. Bu iki fonksiyona da “EnemyHealtBar enemy”i parametre olarak verip, sadece raycast’in çarptığı obje enemy.gameObject’e eşitse true döndürün.

      • niyazi mustafa dedi ki:

        Evet Hocam şu şekilde o metodun kod satırları
        public bool leftHit()
        {
        RaycastHit2D playerRay = Physics2D.Raycast(transform.position, yon = new Vector2(transform.localScale.x, 0), 2f, isEnemy);
        if (playerRay.collider == null)
        {
        return false;

        }
        return true;
        }

        tag olarak degılde layer olarak düşman layerında çarpan yani düşman oluyor sadece düşmana çarptığında true false döndürüyor.

      • yasirkula dedi ki:

        Onu şöyle değiştirebilirsiniz:

        public bool leftHit(EnemyHealtBar enemy)
        {
        RaycastHit2D playerRay = Physics2D.Raycast(transform.position, yon = new Vector2(transform.localScale.x, 0), 2f, isEnemy);
        return playerRay.collider != null && playerRay.collider.gameObject == enemy.gameObject;
        }

        Ama bence daha güzeli, “Fire1” input’u true olduğu vakit, yine böyle raycast’lerle yanlardaki düşmanları (varsa) bulun ve bu düşmanların canını azaltın. Yani can azaltma kodunu düşmana değil player’a verin. Örneğin şöyle bir kod:

        Collider col = playerRay.collider;
        if( col != null )
        {
        EnemyHealtBar dusman = col.GetComponent<EnemyHealtBar>();
        if( dusman != null )
        {
        // Burada dusman objesinin canını azaltın
        }
        }

      • niyazi mustafa dedi ki:

        Hocam problemi buldum
        healtbar kullanmayip public olarak yada float olarak sadece deger verip inspectorde vurdufum zaman o enemynin cani gidiyor ama healtbari aktif etrigimde butun healtbarlar vurdugum deger kadar eksiliyor ve haliyle hepsi ölüyor

      • niyazi mustafa dedi ki:

        Hocam aksam eve gittiğimde değişiklikleri yapıp size döneceğim

      • niyazi mustafa dedi ki:

        hocam şu kısmı nerede ve nasıl kullanacağım anlayamadım
        Collider col = playerRay.collider;
        if( col != null )
        {
        EnemyHealtBar dusman = col.GetComponent();
        if( dusman != null )
        {
        // Burada dusman objesinin canını azaltın
        }
        }

      • yasirkula dedi ki:

        leftHit ve rightHit’in içine yapıştırıp, Player’ın Update fonksiyonunda CrossPlatformInputManager.GetButtonDown(“Fire1”) true ise bu iki fonksiyonu da çağırabilirsiniz. Sadece orada belirttiğim yere EnemyHealtBar’ın canını azaltan bir kod yazmanız lazım. Ve EnemyHealtBar’da artık şu kodu çalıştırmamanız:

        if (PlayerController.instance.DusmanYakinda(this) && CrossPlatformInputManager.GetButtonDown(“Fire1”))
        {
        // blabla
        }

  18. niyazi mustafa dedi ki:

    Merhaba oyun içi camera optimizasyonunu nasıl sağlarız. Her ekranda aynı görüntüyü nasıl sağlarız UI elemanları için söylemiyorum oyun içi görüntüden bahsediyorum yardımcı olabilecek birileri varmıdır.

    • yasirkula dedi ki:

      Aynı görüntüden kastınız, grafik kalitesi olarak mı aynı (gölge kalitesi vs.) yoksa ekranın sağında ve solunda kalan objelerin gözüküp gözükmemesi olarak mı aynı?

      • niyazi mustafa dedi ki:

        her ekranda ayni cozunurlugu nasil sunarim bunun cozumunu bulamadim bir cok yontem denedim ama maalesef bir sonuca varamadim

      • yasirkula dedi ki:

        Diyelim 100×200 çözünürlüklü bir ekran ile 100×300 çözünürlüklü bir ekran olsun. Bu iki ekran arasında 100 piksellik yükseklik farkı var. Bu iki ekranda aynı çözünürlüğü nasıl vermek istiyorsunuz? Görüntü 100×300 çözünürlüklü ekranda uzatılmış/gerilmiş olarak mı gözüksün istiyorsunuz? İsterseniz oyunun başında Screen.SetResolution fonksiyonu ile ekranı belli bir çözünürlüğe ayarlayabilirsiniz.

      • niyazi mustafa dedi ki:

        haklısınız evet biliyorum ama nasıl optimize edeceğiz bu sorunu nasıl aşarız

      • yasirkula dedi ki:

        Görüntü uzatılmış/gerilmiş dursun fark etmez diyorsanız Screen.SetResolution fonksiyonunu ile ekran çözünürlüğünü istediğiniz çözünürlük yapmayı deneyebilirsiniz: https://docs.unity3d.com/ScriptReference/Screen.SetResolution.html

  19. Uğur dedi ki:

    Hocam halen oyunun açılış ekranından ana menüye girişi 5-10 saniye sürüyor ana menüden oyuna geçmesi bazen daha da uzun olabiliyor ve oyun oynarken sürekli kasıyor bilgisayarda unity deyken kasmıyor ama telefonda kasmaya başlıyor bu sorunu nasıl çözebilirim?

  20. Ömer dedi ki:

    Hocam mesela bir uygulamayı apk olarak çıkardığımızda kullanmadığımız gereksiz sahnelerde onunlar birlikte buid oluyor eğer build settings deki scenes ın build bölümünde ekliyse bu performansı olumsuz etkiler mi yoksa etkisi olmaz mı?

  21. ahmet dedi ki:

    Merhaba, 2 boyutlu içinde sadece Sprite Renderer bulunan bir objenin scale ile en ve boyunu arttırmanın performansa herhangi bir etkisi var mıdır acaba? 1×1 beyaz renk 2d bir obje ile aynı objenin 1000×1000 scale edilmiş halinin performansa etkisi aynı mıdır acaba?

    • yasirkula dedi ki:

      Overdraw diye bir olay var, objelerin pikselleri ne kadar üst üste denk gelirse grafik kartı o kadar yorulur. Ama tek bir objenin 1000×1000 olmasının bir etkisi olacağını sanmıyorum.

  22. Squ4re dedi ki:

    Hocam benim bilgisayarda unity çok kasıyor.Kasmayı azaltmak için neler yapabilirim ?

    • yasirkula dedi ki:

      Edit-Project Settings-Quality’den düşük bir grafik ayarı seçmeyi deneyebilirsiniz. Buna rağmen boş bir projede bile program kasıyorsa, bilgisayarınızın güçsüz olması ihtimali de mevcut.

  23. alparslan bilgin dedi ki:

    merheba hocam oyun mobilde çok fazla kasma yapıyor istatistiklerde Tris değeri 750 k verts değeri 380 k bu durum sebep olabilirmi kasma sorununa

    • yasirkula dedi ki:

      Evet olabilir, modelleri olabildiğince low-poly yapmaya çalışın. Buna ilaveten, kullandığınız materyallerin shader’ları da çok önemli. Standard shader kullanmanızı hiç önermem, onun yerine Legacy-Diffuse gibi shader’lar kullanın. Eğer sahnenizde çok fazla model varsa LOD sistemini de inceleyebilirsiniz.

  24. volkan dedi ki:

    Merhaba,sorum apk boyutunun yüksek olmasıyla ilgili.Basit bir oyun yapsam bile boyutu gereksiz büyük oluyor(25-30mb).Sıkıştırma yöntemlerini doğru yaptığımı düşünüyorum. Bu yüzden boş bir proje açıp içine hiç bir şey eklemeden build yaptım ve oluşturduğum apk 15mb geldi.(sadece armv7 seçili, x86 kapalı).Projenin içinde hiçbir şey yokken bu kadar yer kaplamasına çözüm bulamadım. İnternetteki birçok kaynağa bakmama rağmen çözümü yok. Sizin bir çözümünüz varsa şimdiden teşekkür ederim.

    • yasirkula dedi ki:

      Unity’nin her yeni sürümünde boş APK’nın boyutu maalesef artıyor çünkü Unity’nin kütüphanesi genişliyor. İsterseniz Mono yerine IL2CPP Scripting Backend’ini kullanmayı deneyebilirsiniz (Player Settings’ten).

  25. huseyin fe dedi ki:

    ben küçük bir mobil oyun yaptım oyunumda reklamlar var ama bazı telefonlarda internet açılınca kasaya başlıyor internet kapalı olunca kasmıyor .
    reklamlar yüklenirken bir sorun oluyor olabilirmi

    • yasirkula dedi ki:

      Reklam kodlarınızı comment’leyip tekrar build alın ve internet açıkken oyunu test edin. Eğer artık kasma olmuyorsa, dediğiniz gibi reklamlar oyunu kastırıyordur. Ama bu kasma genelde birkaç saniye sürer, yani daha ana menüdeyken kasmanın bitmesi lazım normalde.

  26. yusuf dedi ki:

    hocam benim projemde sahne değiştirmek için butona basıyorum bir iki saniye bekleyince sahne değişiyor sanırım ses dosyasından kaynaklı acaba mp3 kullanmam yada audiosource’u yanlış kullanmamdan mı kaynaklanıyor.

  27. esaddkra dedi ki:

    Merhaba. Unity ile yaptığım projede ağaç kesme işlemi yapmak istiyorum. Ağaçları prefab olarak tek tek eklediğim zaman fps konusunda büyük sıkıntı yaşıyorum. Terrain de bulunan ağaç ekle bölümünden oluşturduğum ağaçları eklediğim zaman fps sorunu ortadan kalkıyor ancak bu seferde ağaçlarla etkileşim kuramıyorum. Prefabtan eklediğim zaman oluşan fps sorununu çözebilir miyim veya terrain üzerinden eklediğim ağaçlarla etkileşim kurabilir miyim ?

  28. ahmet dedi ki:

    Ben ilk başta farklı bir oyun yapıyordum. O oyundan vazgeçip daha farklı bir oyun yapmaya karar verdim. Yeni proje açmak yerine aynı proje üzerinden ilerledim. Sahnelerimde kullanmadığım assetsler yok. Fakat daha önce kullanıp sahneden sildiğim assetsler ve onların animasyonları proje klasörümün içinde duruyor.

    • yasirkula dedi ki:

      Kullanmadığınız asset’ler, Resources veya StreamingAssets klasörlerinde olmadığı sürece boyuta etki etmez. Unity sürümünüz güncelse, dediğim asset’i kullanarak hangi asset’lerin oyununuzda yer kapladığını tespit edebilirsiniz.

yasirkula için bir cevap yazın Cevabı iptal et

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 )

Google fotoğrafı

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

Twitter resmi

Twitter 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.