Menu

Bir Oyun Geliştiricinin Notları

cosgun.halil@gmail.com

Obje Havuzu (Object Pool)

Gelin geçmişe gidelim ve ekranda 3 mermiden fazla bulunmasına izin vermeyen Space Shooter’ları hatırlayalım. Neden 3 mermi kısıtlaması getirmiş olabilirler? Elbette cevap gün gibi ortada. Donanım sahnede 3 mermiden fazlasının hareketine izin vermiyordu.

Oyun dünyası, donanım dünyasıyla doğru orantılı olarak büyüdü. Ancak bu şu demek değil, “zaten kasmaz, performassız kod yazsak da olur!”. Bu düşülebilecek en büyük yanlış. Kasmayacak diye savurganca kod yazamayız.

Savurganca kod yazmamanın en temel kurallarından birine değineceğim bugün. Object Pooling. Yani obje havuzu… Yaklaşım bize şöyle der;

  • Eğer bir objeyi kullanacaksan, onu havuzdan al. Kullan ve objeyi yıkmak yerine inaktif ettikten sonra havuza yeniden ekle.

Object Pooling yaklaşımını uygulamak bizlere, bir objeyi yaratmak ve yıkmak döngüsündeki ağır yükten kurtarır. Şöyle bir senaryo yazalım;

  • Sahnede bir silah var.
  • Silah belli aralıklarla sürekli ateş ediyor.

Bu senaryoda program bir saat açık tutulsa, kaç tane obje yaratıp yıkmış olursunuz, bir düşünsenize! Ama object pooling yaklaşımını uygularsanız, 20 kadar mermi ile günü kurtarabilirsiniz. Evet günü! 24 saati!

O halde başlayalım. Şimdi sahneye bir Capsule objesi ekleyin ve Gun scriptini bu objeye tutuşturun. Objeniz tam da aşağıdaki gibi görünmeli. Gun scriptini de hemen altına paylaşıyorum.

  • BulletPool : Kullanılacak olan mermilerin alınacağı ve kullanılmış olan mermilerin ekleneceği havuz.
  • _transform : Gun objesinin Transform’unu sakladık.
  • _firedBullets : Ateşlenen bütün mermileri tutar.
  • _bulletXLimit : mermilerin ekranda gidebileceği en uç nokta.
  • MoveBullets() : _firedBullets listesi içindeki mermilerin Move() metodlarını çalıştırır.
  • CheckBulletLifeTimes(): _firedBullets listesi içindeki mermilerin ekranda ulaşabilecekleri en son noktaya ulaşıp ulaşmadıklarını kontrol eder ve onları kullanımdan kaldırır.

Şimdi de boş bir GameObject yaratın ve BulletPool scriptini bu objeye ekleyin. Bu işlemin ardından da Gun objenize tıklayın ve Gun scriptinde boş görünen BulletPool’a, henüz yaratmış olduğunuz BulletPool objesini sürükleyip bırakın. BulletPool scriptini de paylaşıyorum.

  • BulletPrefab : Yaratılacak her yeni Bullet objesi bu Prefab’den türetilir.
  • _bullets : Havuzdaki, kullanıma hazır mermileri tutar.
  • Init() : _bullets listesini başlatır.
  • Init(int bulletCount) : _bullets listesini başlatır ve içine bulletCount kadar mermi ekler. Böylece oyun başlarken hali hazırda içi dolu bir havuzumuz olur. Havuzu böyle başlatmak kullanıcının ortalama ne kadar mermi kullanabileceği bilindiğinde avantajlı bir yaklaşım olacaktır.
  • AddBulletToPool() : Havuza bullet ekler.
  • CreateBullet() : Yeni mermi yaratır ve yaratılan mermiyi döndürür.
  • GetBullet() : Havuzdan mermi alır. Bu işlem yapılırken, havuz listesinden mermi çıkarılır ve return edilir.

Son adımda ise bir GameObject yaratın ve adını Bullet olarak değiştirdikten sonra Bullet Scriptini bu objeye ekleyin. Bullet objesi şöyle görünmeli. Kodu da hemen altına paylaşıyorum.

  • _transform : Bir objenin Transform’una “transform.gameobject…..” diye her frame’de ulaşmak yerine, Transformu bir kere saklayıp (Cachelemek) öyle kullanmak çok daha akıllıca bir yöntemdir.
  • _speed : Merminin hızı.
  • Move() : Merninin, +X koordinatında her frame’de _speed hızı kadar pozisyonunu öteler.
  • GetPosition() : Merminin pozisyonunu alır.
  • SetPosition() Mermiye poziston ataması yapar.

Artık play diyebilir ve neler olduğunu gözlemleyebilirsiniz. Eğer anlamadığınız yer olursa, bana email atmanız yeterli. Şunu şöyle yapsan daha iyi olur diye bir öneriniz varsa, onu dinlemekten de büyük keyif alırım. Saygılar, sevgiler.

Proje Linki

Ondalık Sayıların Ondalık Kısımlarını Yuvarlama

Bazı ondalık sayıların ondalık kısımları can sıkabiliyor. Örneğin size gereğinden fazla hassasiyetle pozisyon döndüren bir fonksiyon… Bu fonksiyon size ondalık kısmı yalnızca 1 haneden oluşan sayılar döndürsün isteyebilirsiniz. Bunu da System.Math.Round(double number, int digits) fonksiyonu ile kolayca yapabiliyoruz. Sözü fazla uzatmadan mouse’umuzun pozisyonunu yuvarladığımız örneğimizi paylaşalım.

Böylece, faremizin kordinatlarını yuvarlayan bir çalışma yapmış olduk.

Proje Linki

Bir Objenin Bir Vektöre Göre Konumunu Bulma

Karşılaştığım ilk andan itibaren beni heyecanlandıran ve çözdüğümde kendimi harika hissetmeme sebep olan bir problemi paylaşacağım sizlerle. Bir noktanın, bir vektöre göre konumu. Bu nokta vektörün sağında mı, yoksa solunda mı?

Üstteki görselde neler oluyor, biraz ondan bahsedelim. StartPoint’ten EndPoint’e doğru bir doğrumuz var. Bu doğrunun ucu olarak Pointer objesini yerleştirdik. Pointer objesi doğrunun büyüklüğünü anlamamız için bize yardımcı oluyor. Görsel bir yardımcıdan daha fazlası değil. CheckAbleObject ise bu doğrunun sağında mı solunda mı diye kontrol edeceğimiz noktayı temsil ediyor. Solunda ise sarı, sağında ise pembe…

CheckAble Object Sınıfı

CalculateParameters Fonksiyonu

Burada StartPoint ve Endpoint arasındaki uzaklığı, yani _distance’ı buluyoruz. Bu uzaklığı Pointer’ımızı yerleştirmek için kullanacağız. _vector parametremize ise EndPoint ve StartPoint vektörlerini birbirinden çıkararak, noktamızın yerini belirlemek için alacağımız referans olan vektör değerini atıyoruz. Son olarak da _normalizedVector parametresine, vektörümüzü normalize ederek bir birimlik vektör haline getiriyoruz. Bunu Pointer’ımızı _distance değeri kadar öteye yerleştirebilmek için yaptık.

SetPointerPosition Fonksiyonu

A(x,y,z) gibi bir nokta olan EndPoint’e _distance * _normalizedVector değerini ekleyerek, Pointer’ımızı EndPoint noktasından, bulduğumuz vektörün doğrultusunda _distance uzaklık değeri kadar öteye yerleştiriyoruz.

DrawLine Fonksiyonu

StartPoint, EndPoint ve Pointer noktaları arasında bir line çiziyoruz. LineRenderer hakkında bir çalışma daha incelemek isterseniz geçmişte yazdığım bu yazıya bakabilirsiniz.

CalculateDotPositionSign Fonksiyonu

Bütün işi yapan fonksiyona geldik. Bir noktanın bir vektöre göre konumunu inceleyen bir yaklaşım araştırdığımda, şöyle bir kural gördüm.

Sign((Ax-Bx) * (My – Ay) – (By – Ay) *  (Mx – Ax)) : İşaretin sonucunun -1 ya da +1 çıkmasına göre M noktasının AB doğrusunun ne tarafında olduğunu böyle buluyoruz.

Bu işlemi uyguladım ve gözlerimde beliren bir kaç damla mutlulukla, matematiği bir kez daha sevdim.

Bu problemi araştırırken, vektör matematiği hakkında bilgi eksiğim olduğunu fark ettim ve şuradaki Computer Graphics dökümanlarını okumaya başladım. Size de öneriyorum.

Github Proje Linki

LineRenderer’a Bir Liste İçinde Sakladığımız Pozisyonları Nasıl Ekleriz?

Bir LineRenderer’ın pozisyon listesine, bir obje koordinatı listesini yollayacağız.

Gördüğünüz gibi, DrawMap fonksiyonu içine CubeTransforms listemizi yolladık. Yolladığımız bu listenin boyutu kaç ise LineRenderer’ın positions dizisini o kadar genişlettik. Ardından listemizin içinde gezerek, sırası ile pozisyonları LineRenderer’ın pozisyon dizisine set ettik.

Harita verisi veren bir web servisten kordinat verisi çektiğinizi düşünün. Veriyi doğru alıp almadığınızı kontrol etmek için ilk aşamada, aldığınız koordinat verisini LineRenderer kullanarak çizdirebilirsiniz. Eğer sizlerden istek gelirse ilerde bunu nasıl yapacağımızı da anlatabilirim. Ayrıca Baran Kahyaoğlu‘nu takip ederseniz, harita verilerini Unity’de render ederek nasıl harikalar yarattığını görebilirsiniz.

Proje Github Linki

Bir Ray İçin Birden Fazla Layer’ı Göz Ardı Etmek (Ignoring Multiple Layers)

Yolladığımız bazı ray’lerin bazı layerlardaki objelerle etkileşime girmemesini isteriz. Peki bunu nasıl yaparız? Cevabı paylaşıyorum ve ardından kodu açıklıyorum.

RaySource objesinden Target(hedef) objesine doğru bir ray atacağız. Ray’imizin çıkış noktası objemizin orta noktası. Dolayısıyla atacağımız ray objemize de içten değecek. Yani objemizin bulunduğu layer’ı göz ardı etmemiz gerek. İte bu yüzden, Player Layer’ında bulunan objemizi, _ignoredLayers LayerMask’ına ekliyoruz.

Atacağımız ray ile Target objemiz arasında Friend objemiz yer almakta. Friendly Fire kapalı bir oyunda olduğumuzu ve yolladığımız Ray’in de mermi olduğunu düşünün. Bu merminin dost objeye dokunmaması gerekir. Bu yüzden Friend Layer’ını da göz ardı edilen layerlar arasına eklememiz gerekir. Bunu da yukarıda da yer aldığı gibi, şöyle yaptık:

Shot() fonksiyonu ile yolladığımız ray sonucu, ray’imizin dokunduğu objelerin isimlerini Debug.Log ile yazdırdık ve Friendly Fire kapalı bir oyunda en basit yöntemle dost objeleri nasıl göz ardı edeceğimizi öğrenmiş olduk.

Yaptığım tüm küçük çalışmaların linklerini Github’da tutmaktayım. Github’dan projeyi çekip, örnek çalışmaları deneyebilirsiniz.

Projeye buradan ulaşabilirsiniz.

Ortographic Kamera’nın Yükseklik ve Genişlik Değerlerini Bulma

Ortographic bir kameranın yüksekliği ve genişliğini şöyle buluruz.

 

Birden Fazla Değişken Alan Coroutine’lere Bir Bakış

Bir coroutine’i iki şekilde başlatabiliriz. Birinci yöntemi aşağıda paylaşıyorum.

Move coroutine’i player’ımızı verdiğimiz başlangıç noktasından ulaşmasını istediğimiz noktaya doğru hareket ettiriyor. Mouse’umuzun sol click tuşuna bastığımızda coroutine’imizin durdurulmasını ve player’ımızın da durmasını beklemekteyiz. Ancak deneyecek olursanız, coroutine’leri bu yöntemle başlattığınızda durduramadığınızı görürsünüz. Peki bu sorunu nasıl aşacağız?

Coroutine’leri, onlara birden fazla değişken yollayarak, şöyle de başlatabilirsiniz. Bu yöntem ile başlattığınız Coroutine’ler, StopCoroutine(“CoroutineName”) metodu ile durdurulabileceklerdir. Cevabımız aşağıda dans eden kodlar!

Sorularınız olursa hiç çekinmeden cosgun.halil@gmail.com adresine mail atabilirsiniz.