Yine ve yeniden merhabalar,

Bu yazıda, kimilerini kendim tecrübe ettiğim, kimilerini de ordan burdan derlediğim optimizasyon önerilerinde bulunacağım (Unity 3D için). Yeni şeyler öğrendikçe bu yazıyı sürekli 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).

Script Optimizasyonu

Genel Kural-1: Oyununuzda sistemi en çok hangi bileşen(ler)in yorduğunu görmek için her daim Profiler kullanın.

  • 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.
  • Bir scriptten 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. Bunun en yaygın örneği Transform component’i. Her ne kadar Unity 5 ile Transform’un, “transform” değişkeni vasıtasıyla otomatik olarak cache’lendiğini söyleyenler olsa da, forumlarda bunun aksini iddia edenler de var. Ben işi garantiye almak için sık kullandığım her component’i cache’liyorum (değişkene atıyorum).

Optimizasyon öncesi:

void Update()
{
	// İleri yönde hareket et
	transform.Translate( Vector3.forward * Time.deltaTime );
}

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

public void MuzikCal()
{
	GetComponent<AudioSource>().Play();
}

Optimizasyon sonrası:

private Transform cachedTransform;
private Rigidbody cachedRigidbody;
private AudioSource cachedAudioSource;

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

void Update()
{
	// İleri yönde hareket et
	cachedTransform.Translate( Vector3.forward * Time.deltaTime );
}

void FixedUpdate()
{
	// Yukarı yönde güç uygula
	cachedRigidbody.AddForce( Vector3.up );
}

public void MuzikCal()
{
	cachedAudioSource.Play();
}
  • GameObject.Find ile sahnedeki bir objeye erişmek yavaş bir işlemdir. Bunun çok daha optimize versiyonu GameObject.FindWithTag‘dır. Ancak daha da iyi bir optimizasyon, ilgili objeyi bir değişkende tutmaktır.

Optimizasyon öncesi:

void Update()
{
	// Bu obje'yi, Obje2'nin olduğu yere ışınla
	transform.position = GameObject.Find( "Obje2" ).transform.position;
}

void Obje3uDeaktifEt()
{
	GameObject.FindWithTag( "Obje3unTagi" ).SetActive( false );
}

Optimizasyon sonrası:

private Transform cachedTransform;
private Transform obje2ninTransformu;
private GameObject obje3;

void Awake()
{
	cachedTransform = transform;
	obje2ninTransformu = GameObject.Find( "Obje2" ).transform;
	obje3 = GameObject.FindWithTag( "Obje3unTagi" );
}

void Update()
{
	// Bu obje'yi, Obje2'nin olduğu yere ışınla
	cachedTransform.position = obje2ninTransformu.position;
}

void Obje3uDeaktifEt()
{
	obje3.SetActive( 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ıyor, 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.

Eğer dilerseniz kendi oyununuz için özellikle optimize edilmiş pool scriptinizi yazabilirsiniz. Hazır script kullanmak isterseniz internette “unity pool script” diye aratırsanız pek çok kaynak bulabilirsiniz. Ben de bir generic pool script yazdım; eğer dilerseniz kendi oyunlarınızda kullanabilirsiniz: https://github.com/yasirkula/UnityGenericPool

Optimizasyon öncesi:

/*
 * Silah kodu
 */
public class SilahScript : MonoBehaviour
{
	public GameObject patlamaPartikulu;
	public Rigidbody mermiPartikulu;

	public void AtesEt()
	{
		// Yeni bir mermi oluştur
		Rigidbody mermiKlonu = Instantiate( mermiPartikulu, transform.position, transform.rotation ) as Rigidbody;
		
		// Mermiye ileri yönde güç uygula
		mermiKlonu.AddForce( transform.forward * 1000f );
	}

	public void PatlamaEfekti( Vector3 pozisyon )
	{
		// Yeni bir patlama partikülü objesi oluştur
		GameObject patlamaEfekti = Instantiate( patlamaPartikulu, pozisyon, Quaternion.identity ) as GameObject;
		
		// Patlama efektini 10 saniye sonra otomatik olarak yok et
		Destroy( patlamaEfekti, 10f );
	}
}

/*
 * Mermi kodu
 */
public class MermiScript : MonoBehaviour
{
	void Start()
	{
		// Bu mermiyi 3 saniye sonra otomatik olarak yok et
		Destroy( gameObject, 3f );
	}

	void OnCollisionEnter( Collision temasObjesi )
	{
		// Temas edilen obje Dusman ise
		if( temasObjesi.gameObject.CompareTag( "Dusman" ) )
		{
			// Düşmana 10 hasar ver
			temasObjesi.GetComponent<SaglikScript>().HasarVer( 10 );
			
			// Bu mermiyi yok et
			Destroy( gameObject );
		}
	}
}

Optimizasyon sonrası (kendi PoolScript’imi kullandım):

/*
 * Silah kodu
 */
public class SilahScript : MonoBehaviour
{
	private Transform cachedTransform;
	
	public GameObject patlamaPartikulu;
	public Rigidbody mermiPrefab;
	
	// Patlama prefab'ının klonlarını tutan pool (havuz)
	private PoolScript<GameObject> patlamaPartikulHavuzu;
	
	// Mermi prefab'ının klonlarını tutan pool (havuz)
	// Bu havuza MermiScript'ten de erişildiği için kendisi
	// bir public static değişken
	public static PoolScript<Rigidbody> mermiHavuzu;

	void Start()
	{
		cachedTransform = transform;
		
		// Havuzları oluştururken parametre olarak prefab objelerini veriyoruz
		// Böylece havuzdan obje çekmek isteyince havuz boş olsa bile 
		// bu prefab havuz tarafından otomatik olarak Instantiate ediliyor
		patlamaPartikulHavuzu = new PoolScript<GameObject>( patlamaPartikulu );
		mermiHavuzu = new PoolScript<Rigidbody>( mermiPrefab );
	}
	
	public void AtesEt()
	{
		// Havuzdan bir mermi objesi çek
		Rigidbody mermiKlonu = mermiHavuzu.Pop();
		
		// Mermiyi konumlandır
		Transform mermiKlonuTransform = mermiKlonu.transform;
		mermiKlonuTransform.position = cachedTransform.position;
		mermiKlonuTransform.rotation = cachedTransform.rotation;
		
		// Mermiye ileri yönde güç uygula
		mermiKlonu.AddForce( cachedTransform.forward * 1000f );
	}
	
	public void PatlamaEfekti( Vector3 pozisyon )
	{
		// Havuzdan bir patlama partikülü objesi çek
		GameObject patlamaEfekti = patlamaPartikulHavuzu.Pop();
		
		// Patlama partikülünü konumlandır
		Transform patlamaTransform = patlamaEfekti.transform;
		patlamaTransform.position = pozisyon;
		patlamaTransform.rotation = Quaternion.identity;
		
		// Patlama efektini 10 saniye sonra otomatik olarak yok et
		// Bu sefer coroutine kullanmak zorundayız çünkü, Destroy'un 
		// aksine, havuza objeyi belli bir süre sonra atmanın kısa
		// bir yolu yok
		StartCoroutine( PatlamaEfektiYokEtCoroutine( patlamaEfekti ) );
	}
	
	private IEnumerator PatlamaEfektiYokEtCoroutine( GameObject patlamaEfekti )
	{
		// 10 saniye bekle
		yield return new WaitForSeconds( 10f );
		
		// Patlama efektini havuza geri yolla (böylece sonradan havuzdan  
		// tekrar bir patlama efekti objesi çekmek istediğimizde, bu partikül
		// objesini tekrar kullanabileceğiz)
		patlamaPartikulHavuzu.Push( patlamaEfekti );
	}
}

/*
 * Mermi kodu
 */
public class MermiScript : MonoBehaviour
{
	// Start'ın bir coroutine (IEnumerator) olduğuna dikkat edin
	IEnumerator Start()
	{
		// 3 saniye bekle
		yield return new WaitForSeconds( 3f );
		
		// Mermiyi havuza geri yolla
		SilahScript.mermiHavuzu.Push( GetComponent<Rigidbody>() );
	}

	void OnCollisionEnter( Collision temasObjesi )
	{
		// Temas edilen obje Dusman ise
		if( temasObjesi.gameObject.CompareTag( "Dusman" ) )
		{
			// Düşmana 10 hasar ver
			temasObjesi.GetComponent<SaglikScript>().HasarVer( 10 );
			
			// Mermiyi havuza geri yolla
			SilahScript.mermiHavuzu.Push( GetComponent<Rigidbody>() );
			
			// Artık merminin 3 saniye sonra otomatik olarak
			// yok olmasına gerek kalmadı
			StopAllCoroutines();
		}
	}
}
  • 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 o zaman daha hızlı çalışan sqrMagnitude değişkenini kullanmayı düşünebilirsiniz. Bu fonksiyon, 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
	}
}

void BaskaBirFonksiyon()
{
	// 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
	}
}

void BaskaBirFonksiyon()
{
	// 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:

void Update()
{
	// Objeyi saniyede 0.5 birim ilerlet
	transform.Translate( Vector3.forward * Time.deltaTime / 2 );
}

Optimizasyon sonrası:

private Transform cachedTransform;

void Awake()
{
	cachedTransform = transform;,
}

void Update()
{
	// Objeyi saniyede 0.5 birim ilerlet
	cachedTransform.Translate( Vector3.forward * Time.deltaTime * 0.5f );
}
  • SendMessage fonksiyonu da performans için hiç iyi değildir ve mecbur kalmadıkça kullanılmamalıdır. Onun yerine, çağırmak istediğiniz fonksiyona sahip olan component’i bir değişkende tutup bu component üzerinden direkt o fonksiyonu ç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 birkaç satır fazla kod yazın ama 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 );
	}
}

Fizik Optimizasyonu

  • 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. Eğer bu değeri çok artırırsanız fiziksel etkileşimler istendiği gibi çalışmamaya başlar, o yüzden deneye yanıla ince ayar yapmanız en iyisi.
  • 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.
  • 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 tahmin etme durumu, çoğu fiziksel etkileşim için yeterli olur). O halde napıyoruz? Mesh Collider kullanan objeyi Unity’nin sağladığı primitive collider’lar ile taklit etmeye çalışıyoruz. Her bir primitive collider için ayrı bir child GameObject oluşturabileceğiniz gibi tüm primitive collider’ları aynı objeye de uygulayabilirsiniz (size kalmış).
  • Raycast fonksiyonlarınızda 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 daha hızlı sonuç alırsınız (tahmin edebileceğiniz üzere). Ö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 );
}

Grafik Optimizasyonu

Genel Kural-1: Minimum sayıda materyal kullanın ve shader’larınızı olabildiğince basit tutun/seçin.

Genel Kural-2: 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, çanak, çömlek 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.

Genel Kural-3: Game panelinin sağ üstündeki Stats kapalıysa açın ve oyun boyunca bir gözünüz “Batches” ve “SetPass calls“ta olsun. Bu değerler ne kadar az olursa o kadar iyi. Bunu başarmak için de oyununuzda olabildiğince çok obje aynı materyali kullanmalı. Her objenin kendi texture’u var, o zaman napacağım derseniz cevabım “texture atlasing“. İlaveten, çoğu 3D modelleme uygulaması, modeli “vertex color” ile boyamanıza imkan verir. Eğer ki modeliniz düz renklerden oluşuyorsa ona texture vermek yerine vertex color uygulayabilir ve Unity içerisinde de vertex color’ları gösteren bir shader kullanabilirsiniz.

Doğru Bilinen Yanlışlar-1: 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. Yani bu resim dosyanın boyutu bir önem arz etmezken resim dosyasının kalitesi, Unity tarafından oluşturulan texture’un kalitesine direkt etki eder. Benzer şekilde, 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 dönüştürebilirsiniz.

  • Unity 5 ile gelen Standard Shader‘ı minimum düzeyde kullanın. Bu shader, henüz yeni olduğu için yeterince optimize edilmemiş durumda (özellikle mobil için). 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.
  • Özellikle mobilde Image Effect 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).
  • Unity’nin son sürümleri ile gelen dinamik skybox yerine eski usül, cubemap kullanılarak hazırlanmış skybox’lar kullanın. Yeni skybox dinamik olarak oluşturulduğu için performans açısından daha kötü.
  • 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)

  • Yine Window-Lighting‘de “Precomputed Realtime GI” ve “Baked GI” diye 2 seçenek var. Bu seçenekler lightmapping ile alakalılar. 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

  • (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 çok 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.
  • 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 grafik kalitesi olarak build aldığınız platformun varsayılan ayarına geçiş yapın (varsayılan ayar yeşil bir tik ile gösterilir). Ardından “V Sync Count“u “Every V Blank” yapın ve oyunu test edin.
  • Bazen oyununuzdaki bazı dinamik objelerin de gölge düşürmesini isteyebilirsiniz. Bu objeler static olmadığı için bunların gölgelendirmeleri mecburen gerçek zamanlı olarak hesaplanmalı. 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 ışıklandırmalarınızda “Hard Shadows” kullanın.
  • 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.
  • 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 (sprite’lar için geçerli olmayabilir, emin değilim). 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 şansına da sahip olacaksınız. Yok illa ki 75×75 texture kullanacağım diyorsanız da 128-75=53 pixellik boş alanı başka bir texture için kullanmayı düşünebilirsiniz (texture atlasing).

Oyun Boyutu (Build) Optimizasyonu

Genel Kural-1: Oyunlarda en çok boyutu genellikle texture‘lar ve müzik dosyaları kaplar. Daha ufak bir build için bu dosyaların compression ayarlarında olabildiğince ince ayar yapmak isteyebilirsiniz.

  • (Android) Unity’nin son sürümleriyle birlikte x86 işlemcili Android cihazlara da oyunumuzu build alabiliyoruz. Bu her ne kadar kulağa güzel gelse de oyunun x86 işlemci versiyonunu APK’ya eklemek dosya boyutunu ciddi miktarda artırabiliyor. Şu ana kadar gördüğüm her telefon ARM işlemci kullandığı için ben oyunlarımı henüz x86 işlemciye çıkarmıyorum. Bunun için de Player Settings-Other Settings‘teki “Device Filter“ın değerini “ARMv7” yapıyorum. Bu sayede apk’yı fazlalık mb’lardan kurtarıyorum.
  • (Android) Oyunumuzu build alırken, kodumuzda kullandığımız class’ları içeren dll‘ler de apk dosyasına eklenir ancak bu dll’ler ile gelen kullanmadığımız diğer class’lar boş yere fazlalık oluşturur. Bu class’ları Unity’nin build alırken otomatik olarak yok saymasını sağlayan bir özellik var: “Stripping Level” (Other Settings). Bu seçeneği “Strip Assemblies” yaparsanız dll dosyalarındaki fazlalıklar atılır (dökü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). “Micro mscorlib” ile desteklenen fonksiyon ve class’ların listesini şurada bulabilirsiniz: http://docs.unity3d.com/352/Documentation/ScriptReference/MonoCompatibility.html
yorum
  1. Yine çok güzel bir yazı olmuş. Teşekkür ederiz

  2. Kadir Danışman dedi ki:

    Her zamanki gibi çok yararlı bir makale olmuş, ellerinize sağlık 🙂

  3. serhan dedi ki:

    Yasir Güzel Yazı Olmuş Ellerine Sağlık .

    Peki Ben bişey Sormak İstiyorum. Adamlar 30 Levelli Araba Park Etme , yada 10 levelli yük taşıma simulasyon oyunları yapıyorlar , Oyunların Boyutları 100 mb gibi bir yer kaplıyor.

    Ben 3 levelli Bir Oyun Yapıyorum Ayrı ayrı 3 Sahneden Oluşuyo Birbirinden Bağımsız , bir bölümü geçince diğerr bölüme gidiyosun , oyun boyutu 120 mb oluyooo onuda Google Play Yüklemede Kabul Etmiyor.

    Bunun Çözümü ince Detayı ne ??? Bende 15 Levelli Oyun Yapmak İstiyorum ama boyutu çok aşıyor texturelere dikkat etmeme rağmen .

    • yasirkula dedi ki:

      Oyunun boyutunu düşürmek için en çok boyutu hangi asset’lerin kapladığını bulmanız ve bu asset’leri compress veya optimize etmeniz lazım. Genelde bu asset’ler müzik, texture ve 3D modeller olmakta. Ben kendim Asset Store’daki şu eklentiyi kullanarak kimin ne kadar boyut kapladığını öğreniyorum ama bunu öğrenmenin ücretsiz bir alternatif yolu da vardır muhakkak: https://www.assetstore.unity3d.com/en/#!/content/8162

      Oyunun boyutunu düşüremezseniz de oyunu build alırken Player Settings’ten “Split Application Binary” yaparak .apk ve .obb dosyaları oluşturup bu iki dosyayı da Google Play’e yükleyebilirsiniz (bu konuda tecrübem yok, daha başka adımlar da gerekebilir).

  4. Berkay dedi ki:

    Çok yararlı bir yazı olmuş. Teşekkürler.

  5. Barış dedi ki:

    Lod Group hakkında bilgi verebilir misiniz? O da optimizasyon için kullanılabilir mi

  6. ayse dedi ki:

    Merhaba
    Konudan bağımsız olarak unity de networking işlemleriyle ilgili bilginiz var mı acaba

  7. Yusuf Erdoğan dedi ki:

    ellerine sağlık çok faydalı olmuş 🙂

  8. iran dedi ki:

    selamlar
    abi oncelikle sorumu burda sordugum icin uzgunum 😦
    sizin unity notlarinizda okudum Vector3.Normalize() ne ise yarar diye anlamadim.

    siz dediniz li bu komut vectorun uzunlugunu 1 yapae ancak su urnekter olmuyor 😦

    var vektor : Vector3 = Vector3( 1, 2, 2 );
    vektor.Normalize();

    yani 3/2 bir mi yapiyor!?

  9. iran dedi ki:

    tekrar merhaba
    abi uzunluk “Y”e mi diyorsun?
    yoksa x+y?
    yada x+z?

  10. bthnosma dedi ki:

    Merhaba.Yazdığınız object pooling scriptinde populate tam olarak ne işe yarıyor.pop fonksiyonu zaten pooldan çıkarıyor istenilen şeyi yani neden populate etmeliyiz tam anlamadım.ve bir pool da birbirinden farklı objeleri saklayabilir miyiz ?Biraz uzun oldu fakat cevaplarsanız sevinirim.

    • yasirkula dedi ki:

      Pop fonksiyonu eğer pool’da hazırda obje varsa onu döndürüyor, yoksa yeni bir obje Instantiate edip onu döndürüyor. Oyun esnasında maksimum performans için Instantiate’in minimum kullanılması gerektiğinden Populate fonksiyonu koydum. Populate, pool’un içinde belirlenen sayıda kullanıma hazır obje oluşturmaya yarıyor. Eğer oyunun en başında havuz yeterli miktarda obje ile populate edilirse, Pop fonksiyonunun çoğu zaman yeni obje Instantiate etmesine gerek kalmaz.

      Pool’da birbirinden farklı objeler saklayabilirsiniz ancak bu objeleri kodda elle oluşturup havuza Push’lamanız lazım. Pop yaptığınız zaman havuzun en başında hangi obje varsa o obje döndürülür. Bir başka alternatif ise her obje için ayrı bir havuz kullanmak.

      • bthnosma dedi ki:

        Bende ikinci alternatifi düşünüyordum.Yalnız populate ettiğimizde bi nevi o objeyi havuza push etmiş oluyoruz değil mi?Yani sadece objeyi yok etmek istediğimizde mi push edecez yoksa ilk başta bir kere push etmemiz gerekiyor mu?.Cevabınız için saolun.

      • yasirkula dedi ki:

        Sadece objeyi yok ederken push etmeniz yeterli. Populate’in yaptığı şey havuzu kullanıma hazır birkaç obje ile doldurmak. Bu sayede oyun esnasında Pop yapınca havuzda hazırda obje olacağı için Instantiate’e gerek kalmayacak. Tabii çok fazla Pop yaparsanız ve havuz boş olursa illa ki otomatik olarak Instantiate gerçekleşecek.

  11. Mucahit dedi ki:

    Selamun aleykum abi oyunumu google playe yukledim cogu telefonda sorunsuz calisiyo calisiyo ama bazi telefonlarda kasiyo oyun 15 mb sorunu bi turlu cozemedim neyden kaynaklaniyo olabilir

    • yasirkula dedi ki:

      Oyunun boyutuyla alakalı olmamalı. Belki bazı script’ler cihazı çok yoruyordur ya da oyundaki grafik/3D modellerin kalitesi/boyutu/vertex sayısı telefonun zorlanmadan çizebileceği kapasiteyi aşıyordur. Unity editöründeyken Profiler kullanarak en çok kim performans harcıyor görmeye çalışın, belki oradan yola çıkarak bir şeyler bulabilirsiniz.

  12. Halil dedi ki:

    Unity de script açıyorum , visual studio da project yok diyor ve Unity compilerı çalışmıyor nasıl yapabilirim. Yani şöyle tr yazıyorum altta transform diye seçenek gelmiyor .

  13. KeremD dedi ki:

    Ben yapacağım oyun için 1000 i aşkın sprite a ihtiyaç duyuyorum haliyle oyun çok büyük oluyo ben bunu nasıl engelleyebilirim

    • yasirkula dedi ki:

      Sprite’ların Max Size’larını düşürüp “Compression”larını “Normal Quality”e çekebilirsiniz. Aksi taktirde bunun önüne geçmenin bir yolu olduğunu sanmıyorum, sonuçta o sprite’lar bir yerde depolanmak zorunda.

      • KeremD dedi ki:

        Teşekkürler onları yaptım fakat yinede 1000+ sprite cok buyuk oluyo sadece 20 resimle 3 GB buyuyebiliyo ben bu olmaz diye de düşündüm bi an sizin gibi depolanacak yer açısından fakat the higher and lower game e baktım orada oldukca fazla sprite var benim bahsettigimden de fazladır hatta ama boyutu 20Mb bile etmiyor bu neyle olgili olabilir unitynin bir azizligi midir yoksa bir kodlama mı gerektirecek bilginiz varsa aydinlatabilirseniz sevinirim

      • yasirkula dedi ki:

        Oyun offline çalışıyor mu bilmiyorum ama sadece online çalışıyorsa büyük olasılıkla resimleri internetten indiriyordur.

      • KeremD dedi ki:

        Teşekkürler fakat şimdi konudan bağımsız bir sorum ve bir maruzatım olacak Sorum şöyle ki: Benim yüksek skor değişkenim var static yaparak sahne geçişlerinde kaybolmasını engelledim fakat oyunun kapanıp açıldığında da ben aynı şekilde kalmasını istiyorum bunu nasıl yapabilirim Maruzatım ise Google play services altında leaderboard sistemini anlattığınız bir ders yapmayı düşünürmüsünüz eğer konuya hakimseniz bu konu bir çok insana zor geldiğinden hem dersiniz çok ilgi görür hem de bir çok insana yardım etmiş olursunuz.

      • yasirkula dedi ki:

        Yüksekskor kaydetmenin en kolay yolu PlayerPrefs kullanmak, ancak çok güvenli bir yöntem değil. Belki Application.persistentDataPath’te oluşturacağınız bir dosyaya yüksekskoru encrypted bir şekilde kaydetmek biraz daha güvenli olabilir.

        İlerleyen zamanlarda Google Play Games Services ile alakalı bir ders hazırlayabilirim, ancak şu sıralar değil.

      • KeremD dedi ki:

        Tekrardan teşekkürler dediğiniz Application.persistentDataPath e bakacağım fakat bunları yüksek skordan bağımsız olarak oyun kapandığında değerlerin gitmemesini sağlayabileceğimiz başka basit yollar varmı ?

      • yasirkula dedi ki:

        Serialization diye bir konsept var; çeşitli varyasyonları mevcut: xml serialization, binary serialization gibi… Json veri formatını da araştırabilirsiniz.

  14. KeremD dedi ki:

    Yasir bey benim bir sorum olacaktı oyunumda 18 e yakın sahne var hepsi çok güzel çalışırken bir sahnede donma yaşıyorum bu sahneye bağlı script uzun biraz fakat bu sahnedeki scriptte PlayerPrefs gibi fazla CPU yiyen şeyleri kullanmadım ve Profiler penceresinde de değerler normal görünüyo fakat oyunda iken ufak kasma ve donmalar oluyor yardım edersniz sevinirim şimdiden teşekkürler

    • yasirkula dedi ki:

      Belki grafik anlamında takılıyordur. Bunu test etmek için oyun esnasında Game panelinin boyutunu küçültün (böylece ekrana çizilen piksel sayısı azalacak); eğer oyunun takılması azalıyorsa sıkıntı model, materyal, shader ve(ya) texture’lardandır yoksa script’lerdendir.

      • KeremD dedi ki:

        Hocam baktım da galiba kastıran Tris and Verts kısmı stats dan falan o kanıya vardım fakat bunu nasıl önleyebilirim yardımcı olurmsunuz?

      • yasirkula dedi ki:

        O değerler modellerin poligon sayısıyla alakalı. Yapabiliyorsanız modellerin poligon sayılarını düşürebilir, yoksa objelerin materyallerine verdiğiniz shader’ları basitleştirebilirsiniz (mesela Mobile kategorisindeki shader’ları tercih edebilirsiniz). İlaveten, sahnede gölgeler açıksa gölgeleri kapatarak da Tris ve Verts sayısını neredeyse yarıya indirebilirsiniz.

      • KeremD dedi ki:

        Cok tesekkurler bunu hallettim bundan once hemen biraz ustte bir sorum vardi cok fazla resim kullanmakla alakalı oyunum icin col fazla resim gerekiyordu ve boyut cok fazla artiyordi 20 resimde 2 mb civari siz buna cevap verdiniz fakat ona dair bir ufak sorum var resimleri poolingle havuzdan cikartirsam isime yarar mı? Oyunum offline

      • yasirkula dedi ki:

        Pool sistemi sadece RAM kullanımınıza yardımcı olabilir, oyunun boyutuna etki etmez.

  15. xboxlive99 dedi ki:

    Selamun aleyküm hocam, kolay gelsin. Scriptlerin başında “using” ile başlayan kütüphaneleri kullanmak çıktımızın dosya boyutunu etkiliyor mu? Tüm scriptlerimde system.collections kütüphanesini kullanmadığımı gördüm ve sildim. Dosya boyutunu etkiler mi?

  16. Ahmet dedi ki:

    Merhaba abi. öncelikle C# kullanıyorum ve yapmaya çalıştığım projede zamanla skor artıyor. ölüncede ana menu paneli açılıyor ve joystick falan kapanıyor. SceneManager.LoadScene (0); ile sonlandırıyorum. fakat öldükten sonra SceneManager.LoadScene (0); komutunu kullanmadan hemen önce skorumu kaydetmek istiyorum. PlayerPrefs ile denedim araştırdım çözemedim. senden istediğim abi şu son salise skoru kaydedip. SceneManager.LoadScene (0); komutundan sonra tekrar kullanmak. abi nolur yardım edermisin :))

    • Ahmet dedi ki:

      abi lütfen yardım edermisin

    • yasirkula dedi ki:

      En kolay çözüm yolu static bir değişken kullanmak. Örneğin oyunda A scriptiniz varsa bu scripte “public static int skor;” isminde bir değişken ekleyip oyun bitince A.skor = oyunsonuskoru; yaparak oyun sonu skorunu bu değişkene atayın. Ardından ilk scene’den bu skora A.skor diyerek erişebilirsiniz.

  17. Barış dedi ki:

    Unity içindeki shaderlerden hangisinden en iyi kaliteyi alırım?

    • yasirkula dedi ki:

      Unity ile gelen shader’lar arasında en kalitelileri Standard ve Standard (Specular setup) shader’ları. Ama mobilde performans olarak çok iyi oldukları söylenemez.

Bir Cevap Yazın

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

WordPress.com Logosu

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

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 )

Google+ fotoğrafı

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

Connecting to %s