Obje Havuzu (Object Pool)

Unity3D’de Nesneleri yaratmanın ve öldürmenin yükü bir hayli ağırdır. Saniyede yüzlerce nesneye bellekte yer açıp, sonra o nesneleri bellekten tekrar silmek, performans penceresinden bakıldığında pek de hoş karşılanmayabilir. Eğer nesne yaratma ve silme işlemini çok sık yapıyorsanız, Object Pool işte tam da burada yardımınıza koşuyor.

Senaryo

Bir silah nesnesiniz var. Bu silah nesnei bulunduğu noktadan her yöne saniyenin onda biri kadar bir sıklıkla ateş etsin. Mermiler ekrandan çıktıklarında silinsinler.

Adım 1

Senaryomuzu gerçeklemeye, bullet nesnemizi oluşturarak başlayalım. Bullet nesnesini bir Sphere nesnesine ataç ediniz ve bu nesneyi bir prefab haline getiriniz.

Bullet

Bullet nesnemizin Transform component’ini bir _transform değişkeni içinde saklamamızın sebebi, bu component’in position değerine sürekli olarak erişecek olmamız. Eğer bu componenti _transform değişkeni içinde saklamasaydık performans kaybı yaşayacaktık.

Bullet nesnemizin speed ve movement direction değerleri bulunuyor. Speed değişkenine dışardan atama yapılamıyor. Bu sadece bu örnek için geçerli bir durum. Movement Direction yani ilerleme yönü değişkenine ise Gun nesnesi atama yapacak.

Init() fonksiyonunu Bullet nesnemizi başlatmak için çağırıyoruz.

CameraLimits, içinde kameranın pozisyon limitlerini tuttuğumu bir struct. Bu yapıyı CameraManager nesnesinden GetCameraLimits() metodu aracılığıyla alıyoruz.

CameraLimits

Move metodu bullet nesnemizin hareketini sağlıyor. Bullet nesnelerimizin Move metodlarını bir BulletManager çağıracak. Bunun nedenini birazdan paylaşacağım.

Bullet nesnemizin Move metodu içinde CanMove isimli bir metod çağrılıyor. Buradan dönen bool değere göre bullet yaşatılıyor ya da siliniyor. CanMove metodunu incelediğimizde, bu metodun görevinin “Bullet kamera görüşü sınırları içinde mi?” sorusuna yanıt verdiğini görüyoruz. Bu sorunun yanıtı true ise Bullet hareket etmeye devam ediyor. Yanıt false ise Bullet, BulletEventManager’a “ben ekran dışına çıktım!” diye haber veriyor.

Adım 2

Sahneye eklenen Bullet nesnelerinin Move metodlarını kim çağıracak? Bunların kendi Update metodları neden yok? İlk soruya cevap vererek başlayacağım, Bullet nesnelerinin Move metodlarını BulletManager çağıracak. Her Bullet nesnesinin kendi Update metodu olmasındansa, bir BulletManager tarafından çağrılmaları çok daha performanslı bir durum. Ayrıca bu durumun bir artısı daha var. Eğer siz bir anda ekrandaki bütün mermileri durdurmak istiyorsanız(Matrix’teki efsane sahne geldi gözünüze, biliyorum.), BulletManager’da bunu kolayca yapabilirsiniz.

BulletManager

[SerializeField], _bulletsOnScene listesinin içini Unity’nin properties penceresinde gözlemleyebilmek için yazıldı. _bulletsOnScene listesi ise ekrandaki Bullet nesnelerini tutuyor.

BulletManager’ın Start metodunda BulletEventManager nesnemizin OnBulletFired ve OnBulletDeleted eventlarına kaydoluyoruz. Bu iki olayı dinlememiz BulletManager açısından hayati anlam taşıyor. Çünkü bir bullet yaratıldığında bunu _bulletsOnScene nesnemize eklemeli, bir bullet silindiğinde de bunu _bulletsOnScene listemizden silmeliyiz. Hani şu “ben ekran dışına çıktım!” diyen Bullet vardı ya, işte o bullet nesnesinin ekran dışına çıktığını OnBulletDeleted olayını dinlediğimiz için biliyor ve BulletManager içindeki DeleteBullet metodunu çağırabiliyoruz.

Adım 3

Bullet nesnelerinin yaratılması ve silinmesi olaylarını dinleyicilere dağıtan BulletEventManager’ımızı yazalım.

BulletEventManager

OnBulletFired olayını dinleyenler bir bullet nesnesi ateşlendiğini bilecekler. Aynı şekilde OnBulletDeleted olayını dinleyenler bir bullet’ın silinmek üzere olduğunu bilecekler.

Adım 4 – Gelelim Fasulyenin Faydalarına

Artık BulletPool’umuzu yazmaya hazırız. Nesnelerimizin tasarrufunu bu havuzda yapacağız.

BulletPool Singleton bir nesne. Bu durum da bize BulletPool’a kolayca ulaşmamıza olanak veriyor.

BulletPool bullet nesnemizin bir prefab’ini tutuyor. Yaratılacak bütün bullet nesneleri bu prefab’den klonlanacak.

Yaratılmış ve inaktif olarak kullanılmayı bekleyen bullet nesneleri _bulletStack’te tutuluyor.

SetupPool() metodu ile havuzumuzu hazırlıyoruz. Duruma göre biraz dolu bir havuz, bomboş bir havuzdan iyidir. Ancak şuna dikkat etmekte yarar var. Ancak şuna dikkat etmekte yarar var, 50 nesneden fazla kullanmadığımız bir uygulamada havuzumuzu 1000 tane nesne ile başlatırsak, büyük bir hafıza ayırmış ve kaş yapayım derken göz çıkarmış oluruz.

BulletEventManager’ın OnBulletDeleted olayını burada da dinlemişiz. Silinen Bullet’ı havuzumuza eklemek için.

GenerateBullet, yeni bir bullet nesnesi yaratır ve bunu _bulletStack’e ekler. GetBullet() metodu dışardan Bullet istendiğinde bu isteğe cevap vermeye yarar. PoolBullet ise ne zaman bir bullet nesnesi silinse, bunu alır ve inaktif ettikten sonra havuza ekler.

Adım 5

Gun nesnemizi yazalım.

Gun nesnesi Start metodu çağrıldığında ShootTheGun coroutine’ini çalıştırır. Böylece Gun saniyenin yüzde biri aralıklarla ranstgele yöne ateş etmeye başlar. Gun’ın bir bullet nesnesini alıp kullanım dönüsü şöyledir.

BulletPool’dan bir bullet nesnesi ister. Aldığı bullet nesnesine kendi pozisyonunu ve bullet’ın gitmesi gereken yönü atar. Ve son olarak da BulletEventManager’a ben bir bullet ateşledim diye haber verir.

Adım 6 – Demo

Sahneyi sizler için hazırladım. Design Pattern isimli Github projemi indirip inceleyebilirsiniz. Sahnede 25 tane Gun bulunuyor. Play tuşuna bastığınızda 1200 küsür objenin her bir yana saçıldığını görüyoruz. Peki performanstan ne haber? Editörde tam ekran modda 70 – 74 fps…

Design Pattern ile ilgili olan çalışmalarımın Github sayfasına buradan ulaşabilirsiniz.

Şurası da şöyle olsa daha iyi olurdu demekten, bunu yorum olarak belirtmekten lütfen çekinmeyin. Bana cosgun.halil@gmail.com adresinden ulaşmak konusunda da rahat hissetmenizi rica ediyorum.

Eğer bu çalışmam işinize yaradıysa ve daha fazla çalışma yapabilmem için bana destek olmak isterseniz, bir kahvenizi içerim. 🙂

Saygılar.