
Unity’de Nesne Havuzu Oluşturmak (Object Pooling)
Merhaba, bir önceki dersimizde Unity‘de Line Renderer bileşeninin kullanıma bakmıştık.Belirlediğimiz noktalar arasında düz çizgiler oluşturarak, bir ip yapmıştık.Bugünkü dersimizde ise, Unity ile nesne havuzu oluşturacağız.Obje havuzu yani Object Pooling kavramını biraz açalım.Bunun bir örnek ile daha iyi anlaşılacağını düşünüyorum.Unity‘de bir uçak oyunu yapıyorsunuz diyelim.Uçağınız ile ateş ederek düşmanları tek tek düşürüyorsunuz.
Eğer uçağınızdan çıkan mermileri Destroy fonksiyonu ile silip, Instantiate fonksiyonu ile tekrar oluşturuyorsanız, işte bu döngü CPU üzerinde ekstra bir yük yaratacaktır ve projenizi verimsiz bir hale getirecektir.Bu şekilde oluşan performans kaybının önüne geçmek için Object Pooling denilen, obje havuzu yöntemini öğreneceğiz.Eğer buraya kadar kafanızda bir şey canlanmadıysa, birazdan daha iyi anlayacaksınız diye düşünüyorum.Evet lafı uzatmadan yeni bir proje oluşturalım ve dersimize başlayalım.
Sahnemizi oluşturmaya başlayalım.
Evet bu projemizde kullanacağımız senaryoya bir bakalım.Bu projede yüksek şarjör kapasiteli bir silah kullanacağız.Bunun içinde bir Gatling yani çok namlulu makineli bir tüfek kullanacağız.Açıkçası bu proje için en uygun silah bu olur diye düşündüm.Daha sonra sınırlı sayıda oluşturduğumuz mermilerimizi, bu Gatling silahımızdan ateşleyerek bir döngü şeklinde tekrar kullanacağız.
Böylece projemizde gereksiz bir yük oluşturmayacağız.Şimdi sahnemizi oluşturmaya başlayalım.İlk olarak bir Plane nesnesi oluşturuyoruz.Bu nesne bizim zeminimizi oluşturacaktır.Daha sonra internetten bulabileceğimiz bir 3D silah modelini projemize ekliyoruz.Dediğim gibi ben tercihimi, Gatling silahından yana kullandım.Siz istediğiniz bir silahı da seçebilirsiniz.
Projemize eklediğimiz silah modelimizi, sürükleyerek sahnemize bırakıyoruz.Scene penceresinden gerekli boyut ve konum ayarlarını yapıyoruz.Daha sonra Hierarchy penceresinde, silah modelimizin parçalarını hiyerarşik olarak düzenliyoruz.Bunu yapmak zorunda değilsiniz, ancak projenizi ne kadar düzenli tutarsanız o kadar iyi olur.
Silah modelimiz hazır.Şimdi bu silahta kullanacağımız mermiyi oluşturalım.Bunun için, bir Sphere nesnesi de kullanabilirsiniz.Ben ayrıca bir mermi modeli kullanacağım.Mermi olarak kullanacağımız nesneyi sahnemize ekledikten sonra, Scene penceresinden boyutunu ayarlıyoruz.Daha sonra Inspector penceresinden, Rigidbody bileşenini ekliyoruz.
Böylece mermilerimiz fizik unsurlarından etkilenebilecekler.Daha sonra bu nesnemizi seçiyoruz ve Project penceresine sürükleyerek bırakıyoruz.Artık mermimiz için Prefab dosyamız hazır.Şimdi mermilerimizin çıkacağı referans noktayı oluşturmaya başlayalım.Bunun için bir Cube nesnesi oluşturuyoruz.Daha sonra bu Cube nesnemizi Scene penceresinden, silah modelimizin namlusunun önüne konumlandırıyoruz.
Sonra bu nesne için yeni bir etiket oluşturuyoruz. Şimdi istediğimiz şekilde ayarladıktan sonra, bu nesnemizi seçiyoruz.Inspector penceresinde bulunan Box Collider, Mesh Renderer ve Mesh Filter bileşenlerini siliyoruz.Tüm bunlar yerine bir boş nesne kullanarak da yapabilirdik.Ancak o zaman konumlandırma işini düzgün yapamayabilirdik.Cube nesnesi kullanarak, konumlandırma işini daha doğru ve görerek yapıyoruz.
Son olarak projemizin başlangıcında oluşturacağımız, mermilerimizi bir boş nesne altında toplamamız gerekiyor.Böylece daha düzenli bir görüntü olacak ve nesne havuzunda ki değişimleri daha iyi görebileceğiz.Bunu yapmak için, Hierarchy penceresinde boş bir nesne oluşturuyoruz.Daha sonra bu boş nesne için yeni bir etiket oluşturuyoruz ve nesnemize ekliyoruz.Artık kodlarımızı yazmaya başlayabiliriz.
Kodlarımızı yazmaya başlayalım.
İlk olarak iki adet C# Script dosyası oluşturuyoruz.Bunlardan birini nesne havuzumuzu yapmak ve ateş etme mekaniğini oluşturmak için kullanacağız.Diğerini ise, mermilerimizi kontrol etmek için kullanacağız.Şimdi ilk script dosyamızı editör ile açalım ve kodlarımızı yazmaya başlayalım.
public GameObject SilahMermisi; public Transform SilahMermiNoktasi; public Transform MermileriBiriktir; public int MermiSayisi;
Değişkenlerimizi oluşturmakla başlıyoruz.İlk satırda, bir GameObject değişkeni oluşturuyoruz.Bu değişkende, Prefab dosyamızı kontrol edeceğiz.İkinci satırda, bir Transform değişkeni oluşturuyoruz.Bu değişkenle ise, mermilerimizin çıkacağı referans noktayı kontrol edeceğiz.
Üçüncü satırda yine bir Transform değişkeni oluşturuyoruz.Bu değişkende, mermi klonlarımızı içinde tutacağı boş nesnemizi kontrol edeceğiz.Son satırda ise, int (integer) türünde bir değişken oluşturuyoruz.Bu değişkende ise, mermi sayımızı kontrol edeceğiz.
public List Mermiler; public mermi mermiScripti; public Transform SiradakiMermiPozisyonu; public Rigidbody SiradakiMermiFizigi; public float AtisAraligi;
Değişkenlerimizi oluşturmaya devam ediyoruz.İlk satırda yeni bir GameObject listesi oluşturuyoruz.Bu liste içerisinde mermilerimizi kontrol edeceğiz.İkinci satırda ikinci script dosyamız için bir değişken oluşturuyoruz.Üçüncü satırda, bir Transform değişkeni oluşturuyoruz.
Bu değişkende, mermilerimizin pozisyonunu kontrol edeceğiz.Dördüncü satırda, bir Rigidbody değişkeni oluşturuyoruz.Bu değişkenle ise, mermilerimizin fiziğini kontrol edeceğiz.Son satırda ise float türünde bir değişken oluşturuyoruz.Bu değişkende, silahımızda çıkacak mermilerin aralığını kontrol edeceğiz.
SilahMermiNoktasi = GameObject.FindWithTag ("cikis_noktasi").transform; MermileriBiriktir = GameObject.FindWithTag ("mermi").transform;
Şimdi bu kodları start() fonksiyonumuzun içerisine yazıyoruz.İlk satırda değişkenimize, etiketi “cikis_noktasi” olan nesnenin transform değerlerini eşitliyoruz.İkinci satırda yine değişkenimize, etiketi “mermi” olan nesnemizin transform değerlerini eşitliyoruz.
Mermiler = new List(); for (int i = 0; i < MermiSayisi; i++) { }
Bu kodları start() fonksiyonumuzun devamına yazıyoruz.İlk satırda, değişkenimize yeni bir GameObject listesi tanımlıyoruz.İkinci satırda ise, bir for döngüsü oluşturuyoruz.Böylece projemizi başlattığımız zaman, istediğimiz miktarda mermi oluşturabileceğiz.
GameObject YeniMermi = Instantiate (SilahMermisi, SilahMermiNoktasi.transform.position, SilahMermisi.transform.rotation) as GameObject; YeniMermi.name = "Mermi" + i; YeniMermi.transform.SetParent (MermileriBiriktir); YeniMermi.SetActive(false); Mermiler.Add(YeniMermi);
Şimdi az önce oluşturduğumuz for döngümüzün içerisine bu kodları yazıyoruz.İlk satırda yeni bir GameObject değişkeni oluşturuyoruz.Bu değişkene, Instantiate fonksiyonunu kullanarak yeni mermi klonlarını eşitliyoruz.Parametrelerine bakacak olursak ilk bölümde, Prefab nesnemizi ekliyoruz.İkinci bölümde, mermimizin çıkacağı referans noktayı tutan değişkenimizi ekliyoruz.
Son bölümde ise, Prefab mermimizin rotasyon değerlerini ekliyoruz.Üçüncü satırda, oluşan klon mermilerine yeni isimler veriyoruz.Sondaki “i” değişkeni ile, benzersiz isimler oluşturuyoruz.Yani “Mermi1”, “Mermi2”, “Mermi3” şeklinde.Dördüncü satırda ise SetParent fonksiyonunu kullanarak oluşan klon mermilerimizi, parantez içerisinde yer alan nesnenin çocuk nesnesi haline getiriyoruz.
Böylece klon nesnelerimiz bir arada ve daha kontrollü bir şekilde oluyor.Beşinci satırda oluşan her bir mermi klonunu, sahnemizde gizliyoruz.Bunu SetActive fonksiyonu ile yapıyoruz.Son satırda ise her bir klon mermimizi, oluşturmuş olduğumuz “Mermiler” listemize ekliyoruz.Bunu yapmak içinde Add fonksiyonunu kullanıyoruz.
if (Mermiler.Count != 0) { mermiScripti = Mermiler [0].GetComponent (); SiradakiMermiPozisyonu = Mermiler [0].GetComponent (); SiradakiMermiFizigi = Mermiler [0].GetComponent (); }
Evet bu kodları start() fonksiyonunun devamına yazıyoruz.İlk satırda yeni bir koşul oluşturuyoruz.Eğer “Mermiler” listemizin eleman sayısı sıfır “0” değilse, koşulumuz doğru oluyor ve içerisindeki kodları çalıştırıyor.İkinci satırda değişkenimize, “Mermiler” listemizde yer alan ilk elemanın, bir başka değişle index numarası sıfır “0” olan elemanın, “mermi” bileşenini tanımlıyoruz.
Böylece ikinci script dosyamızdan veri çekebileceğiz.Üçüncü satırda değişkenimize, yine “Mermiler” listesindeki ilk elemanın Transform bileşenini tanımlıyoruz.Dördüncü satırda ise değişkenimize, “Mermiler” listesinde index numarası sıfır “0” olan elemanın Rigidbody bileşenini tanımlıyoruz.
Nesne havuzumuzu oluşturmaya başlayalım.
void MermiHavuzu() { if (Mermiler.Count != 0) { mermiScripti = Mermiler [0].GetComponent (); mermiScripti.MermiDurumu (); SiradakiMermiPozisyonu = Mermiler [0].GetComponent (); SiradakiMermiFizigi = Mermiler [0].GetComponent (); } }
Şimdi yeni bir fonksiyon oluşturuyoruz.Bu fonksiyonumuzda, mermilerimizin bileşenlerini kontrol edeceğiz ve ateşleme mekaniği için gerekli yapıyı oluşturacağız.İkinci satırda yeni bir koşul oluşturuyoruz.Bu koşulu ve içerisindeki kodları az önce, start() fonksiyonunda değindik.Bu yüzden, sadece dördüncü satırı anlatacağım.Çünkü bu satırı yeni ekledik.
Dördüncü satırda, ikinci script dosyamıza erişmek için oluşturduğumuz değişkeni kullanarak, “MermiDurumu()” fonksiyonuna ulaşıyoruz.Bu fonksiyonun ne işe taradığını birazdan ikinci script dosyamızı oluştururken anlatacağım.O yüzden şimdi geçiyorum.
SiradakiMermiPozisyonu.position = SilahMermiNoktasi.transform.position; SiradakiMermiPozisyonu.rotation = SilahMermisi.transform.rotation; SiradakiMermiFizigi.velocity = Vector3.zero; SiradakiMermiFizigi.AddRelativeForce (Vector3.forward * 5000);
Fonksiyonumuzun devamına bu kodları yazıyoruz.İlk satırda değişkenimizin pozisyonunu, referans noktasının pozisyonuna eşitliyoruz.İkinci satırda yine değişkenimizin rotasyonunu, mermimizin rotasyon değerlerine eşitliyoruz.Böylece her bir mermi sırası geldiği zaman, belirlediğimizi noktaya ışınlanacaktır.
Üçüncü satırda ise değişkenimizin hızını sıfırlıyoruz.Böylece her bir mermi ateşlenmeden yani bir kuvvet uygulanmadan önce sıfırlanmış oluyor.Son satırda ise, AddRelativeForce fonksiyonu ile belirli bir yönde ve hızda kuvvet uyguluyoruz.
Ateş etme ve mermi değiştirme mekaniğini oluşturalım.
if (Input.GetMouseButton(0)) { AtisAraligi += Time.deltaTime; if (AtisAraligi > 0.05f) { MermiHavuzu (); } }
Şimdi bu kodları update() fonksiyonumuzun içerisine yazıyoruz.İlk satırda yeni bir koşul oluşturuyoruz.Eğer faremizin sol tuşuna basarsak, koşulumuz aktif oluyor.İkinci satırda değişkenimizin değerini, Time.deltaTime fonksiyonu ile arttırmaya başlıyoruz.
Üçüncü satırda, tekrar yeni bir koşul oluşturuyoruz.Eğer değişkenimizin değeri “0.05f” değerinden büyük ise, koşulumuz aktif oluyor.Buradaki “0.05f” değeri, mermilerimizin ateşleme aralığını belirliyor.Bu değeri arttırarak, daha yavaş ateş edebilirsiniz.Dördüncü satırda ise, az önce oluşturduğumuz fonksiyonumuzu çağırıyoruz.
GameObject YeniMermi = Mermiler [0]; Mermiler.RemoveAt (0); Mermiler.Add (YeniMermi); AtisAraligi = 0;
Buraya kadar silahımızdan mermileri ateşleyebiliyoruz.Ancak mermilerimizin bulunduğu listeyi güncel tutmazsak, sürekli olarak aynı mermiyi kullanarak ateş etmek durumunda kalırız.Bu bizim istediğimiz bir sonuç değil.Bunu önlemek ve listedeki her bir mermiyi kullanmak için, bu kodları az önce oluşturduğumuz ikinci fonksiyonumuzun devamına yazıyoruz.İlk satırda yeni bir GameObject değişkeni oluşturuyoruz.
Bu değişkenimize, “Mermiler” listesindeki ilk elemanı tanımlıyoruz.İkinci satırda ise RemoveAt fonksiyonunu kullanarak, “Mermiler” listesinde index numarası sıfır “0” olan elemanı siliyoruz.Üçüncü satırda Add fonksiyonu ile “YeniMermi” değişkeninde tanımlı olan elemanı, listemizin sonuna ekliyoruz.Böylece ateş ettiğimiz her bir mermi, listemizin sonuna eklenerek devam ediyor.Son satırda ise, “AtisAraligi” değişkeninin değerini sıfırlıyoruz.Böylece atış aralığı için süre tekrar başlıyor.
if (Input.GetMouseButtonUp(0)) { AtisAraligi = 0; }
Son olarak update() fonksiyonumuzun devamına bu kodu yazıyoruz.İlk satırda yeni bir koşul oluşturuyoruz.Eğer faremizin sol tuşuna basmayı bırakırsak, koşulumuz aktif oluyor.İkinci satırda ise, “AtisAraligi” değişkeninin değerini sıfırlıyoruz.Böylece bu değişkendeki süre değeri artmaya devam etmiyor.
Her bir mermiyi kontrol edelim.
Şimdi ilk script dosyamız hazırlandı.İkinci script dosyamızı editör ile açalım ve kodlarımızı yazmaya başlayalım.
public float Sure; public float MaksimumSure;
İlk olarak değişkenlerimiz oluşturuyoruz.İlk satırda, float türünde bir değişken oluşturuyoruz.Bu değişken ile mermilerimizin aktif kalma süresini kontrol edeceğiz.İkinci satırda yine float türünde bir değişken oluşturuyoruz.Bu değişkenle ise, mermilerimizin ne kadar aktif kalacağını belirleyeceğiz.
gameObject.SetActive (false); MaksimumSure = 2f;
Şimdi bu kodları start() fonksiyonumuzun içerisine yazıyoruz.İlk satırda, script dosyamızın ekli olduğu nesnemizi pasif duruma getiriyoruz.Yani, sahnemizde gizliyoruz.İkinci satırda ise, değişkenimize bir değer veriyoruz.Bu değer mermilerimizin sahnemizde aktif olarak bulunacağı maksimum zamanı işaret ediyor.
public void MermiDurumu() { gameObject.SetActive (true); }
Daha sonra yeni bir fonksiyon oluşturuyoruz.Bu fonksiyonda, mermilerimizin sahnedeki aktiflik durumunu kontrol edeceğiz.İkinci satırda, script dosyamızın ekli olduğu nesnemizi aktif duruma getiriyoruz.
if (gameObject.activeSelf) { Sure += Time.deltaTime; if (Sure > MaksimumSure) { gameObject.SetActive (false); Sure = 0; } }
Şimdi gelelim update() fonksiyonumuza yazacağımız kodlara.İlk satırda, yeni bir koşul oluşturuyoruz.Eğer script dosyamızın ekli olduğu nesne, sahnede aktif ise koşulumuz doğru oluyor.İkinci satırda değişkenimizin değerini, Time.deltaTime fonksiyonu ile arttırmaya başlıyoruz.Üçüncü satırda yeni bir koşul oluşturuyoruz.
Bu koşulda ise eğer, değişkenimizin değeri “MaksimumSure” değişkeninin almış olduğu değerden büyük ise, koşulumuz aktif oluyor.Dördüncü satırda, script dosyamızın ekli olduğu nesneyi pasif durumuna getiriyoruz.Yani sahnemizde gizliyoruz.Beşinci satırda ise, “Sure” değişkenimizin değerini sıfırlıyoruz.Böylece süremiz baştan başlıyor.Bu bölümde ne yaptığımızı biraz açalım.Çünkü önemli bir bölüm.
Biz silahımızdan mermileri ateşledikten sonra, süremiz başlıyor ve bu süre belirlediğimiz limitlere ulaşana kadar, mermi nesnemiz sahnemizde gözükmeye devam ediyor.Süre bittiğinde ise, bu mermimiz sahnemizde gizleniyor.İşte böylece tekrar tekrar mermi klonlamak zorunda kalmadan, elimizdeki mermileri kullanıyoruz.Evet ikinci script dosyamızda hazır.
Projemizin sonuna geldik.Şimdi oluşturmuş olduğumuz bu iki C# Script dosyamızı, gerekli yerlerine ekleyelim.İlk script dosyamızı, silah modelimizin ebeveyn nesnesinin üzerine sürükleyip bırakıyoruz.Daha sonra Project penceresinde bulunan Prefab dosyamızı sürükleyerek, script dosyamızdaki ilgili bölüme ekliyoruz.İkinci script dosyamızı ise, Project penceresinde bulunan Prefab dosyamızın üzerine sürükleyip bırakıyoruz.Artık test etmeye hazırız.Ben ekstra olarak mekanikler de ekledim.Bu mekanikleri daha önceki yazılarda anlatmıştım.İsteyen geçmiş yazılara bakabilir.Evet bir sonraki yazıda görüşmek üzere…