
Unity'de Object Pooling nedir
Unity'de FPS Düşüşlerine Son: Object Pooling ile Performansınızı Tavan Yaptırın!
Herkese selamlar değerli oyun geliştiricileri ve Unity tutkunları! Ben Mert Kıyak.
Oyun geliştirirken hepimizin en az bir kere karşılaştığı, o moralleri bozan anı çok iyi biliyorsunuz: Ekranda yüzlerce mermi uçuşuyor, patlamalar ardı ardına gerçekleşiyor ve pat... FPS aniden düşüyor!
Profiler'ı açtığınızda ise suçlunun o meşhur sarı pikler, yani Garbage Collector (Çöp Toplayıcı) olduğunu görüyorsunuz.
Peki, yüzlerce nesneyi aynı anda yaratıp yok ederken oyunumuzun tereyağı gibi akmasını nasıl sağlarız? Cevap: Object Pooling (Nesne Havuzlama) Tekniği.
Bugün sizlerle Unity'de performansı artırmanın en etkili yollarından biri olan Object Pooling mantığını ve Unity’nin bize sunduğu modern `UnityEngine.Pool` kütüphanesini inceleyeceğiz. Hazırsanız, kahvelerinizi alın ve başlayalım!
Instantiate ve Destroy Neden Can Düşmanımız?
Bir mermi sıkıldığında `Instantiate()` kullanıp, duvara çarptığında `Destroy()` demek ilk aşamada çok pratik gelir. Ancak arka planda gerçekleşen olay şudur:
1. `Instantiate()`: RAM'de yeni bir alan tahsis eder (Allocation). Bu işlem işlemci için maliyetlidir.
2. `Destroy()`: Nesneyi bellekten hemen silmez, sadece "silinecek" olarak işaretler.
3. Garbage Collector: Belirli aralıklarla gelir ve bu silinen nesnelerin artıklarını bellekten temizler. GC çalıştığı an oyununuzda mikrosaniyelik (bazen milisaniyelik) takılmalar (stuttering) yaşanır.
Analoji Zamanı:
Her gün restorana gittiğinizi düşünün. Yemek yedikten sonra tabağı çöpe atıp, bir sonraki yemek için yeni bir tabak satın alır mısınız? Hayır! Tabağı yıkar ve tekrar kullanırsınız.
İşte Object Pooling, tam olarak bu "tabakları yıkama ve tekrar kullanma" mantığıdır.
---
Object Pooling Nasıl Çalışır?
Oyun başladığında (veya ihtiyaç anında) belirli sayıda nesneyi (örneğin 50 adet mermi) bir kez oluştururuz ve bunları pasif hale getirip bir "havuzda" saklarız.
* Mermiye ihtiyaç duyulduğunda havuzdan alır, aktif ederiz.
* Merminin işi bittiğinde yok etmek yerine pasif yapıp havuza geri göndeririz.
Böylece `Instantiate` ve `Destroy` işlemlerini neredeyse sıfıra indirmiş oluruz!
---
Modern Yöntem: Unity’nin Yerleşik `UnityEngine.Pool` Kullanımı
Eskiden kendi ScriptableObject veya List tabanlı havuz sistemlerimizi yazardık. Ancak Unity 2021 ile birlikte harika bir dahili kütüphane geldi: `UnityEngine.Pool`.
Gelin basit bir Mermi Havuzu (Bullet Pool) yazalım:
using UnityEngine;
using UnityEngine.Pool;
public class BulletSpawner : MonoBehaviour
{
[SerializeField] private Bullet bulletPrefab;
[SerializeField] private int defaultCapacity = 20;
[SerializeField] private int maxSize = 100;
private IObjectPool<Bullet> bulletPool;
private void Awake()
{
// ObjectPool'u başlatıyoruz
bulletPool = new ObjectPool<Bullet>(
createFunc: CreateBullet, // Havuzda nesne yoksa yeni üretme fonksiyonu
actionOnGet: OnGetBullet, // Havuzdan nesne çekildiğinde ne yapılacak?
actionOnRelease: OnReleaseBullet, // Nesne havuza geri atıldığında ne yapılacak?
actionOnDestroy: OnDestroyBullet, // Havuz kapasitesi aşılırsa ve nesne silinecekse
collectionCheck: true, // Hatalı çift geri bildirimi kontrol et (Development aşamasında harika!)
defaultCapacity: defaultCapacity,
maxSize: maxSize
);
}
// --- Havuz Metotları ---
private Bullet CreateBullet()
{
Bullet bullet = Instantiate(bulletPrefab);
bullet.SetPool(bulletPool); // Mermiye hangi havuza ait olduğunu bildiriyoruz
return bullet;
}
private void OnGetBullet(Bullet bullet)
{
bullet.gameObject.SetActive(true); // Nesneyi görünür yap
}
private void OnReleaseBullet(Bullet bullet)
{
bullet.gameObject.SetActive(false); // Nesneyi gizle
}
private void OnDestroyBullet(Bullet bullet)
{
Destroy(bullet.gameObject); // Havuz sınırı aşıldıysa gerçekten sil
}
// --- Mermi Atış Fonksiyonu ---
public void Fire()
{
// Instantiate yerine havuzdan çağırıyoruz!
Bullet bullet = bulletPool.Get();
bullet.transform.position = transform.position;
bullet.transform.rotation = transform.rotation;
}
}
Mermi sınıfımızın içinde ise işi bittiğinde kendini havuza iade edecek yapıyı kuruyoruz:
using UnityEngine;
using UnityEngine.Pool;
public class Bullet : MonoBehaviour
{
private IObjectPool<Bullet> pool;
public void SetPool(IObjectPool<Bullet> poolRef)
{
pool = poolRef;
}
private void OnCollisionEnter(Collision collision)
{
// Destroy(gameObject) YERİNE:
// Mermiyi havuza geri iade ediyoruz!
pool.Release(this);
}
}
Object Pooling Kullanırken Dikkat Etmeniz Gereken 3 Altın Kural
1. State Resetting (Durum Sıfırlama): Bir nesneyi havuzdan tekrar aldığınızda eski verileri kalmış olabilir. Canını, hızını veya rengini `actionOnGet` içerisinde mutlaka sıfırlayın!
2. Abartmayın: Her GameObject için havuz oluşturmanıza gerek yok. Sadece oyun esnasında çok sık yaratılan ve yok edilen (mermiler, efektler, particle'lar, basit düşmanlar) nesneler için kullanın.
3. Doğru Kapasite Seçimi: `defaultCapacity` ve `maxSize` değerlerini doğru ayarlayın. Eğer maksimum sınırı çok düşük tutarsanız Unity arka planda yine `Destroy` çağırmak zorunda kalır.
---
Özet ve Kapanış
Object Pooling, özellikle mobil cihazlara veya düşük sistemli bilgisayarlara oyun geliştiriyorsanız hayat kurtarıcı bir optimization tasarım kalıbıdır (design pattern). Unity'nin yerleşik `UnityEngine.Pool` paketi sayesinde artık bu sistemi kurmak sadece birkaç dakikanızı alıyor.
Projelerinizde Object Pooling kullanıyor musunuz? Ya da karşılaşabileceğiniz sorunlar var mı? Yorumlarda benimle paylaşmayı unutmayın!
Bir sonraki Unity & Oyun Geliştirme yazısında görüşmek üzere, kodlarınız hatasız, FPS’iniz bol olsun!
— Mert Kıyak
