Unity’de Tank Yapımı 2 (Ateş Etme & Tepme)
Merhaba, bir önceki dersimizde Unity‘de tank yapımına başlamıştık.Projemizin ilk bölümünde, tankımızın hareket ve kamera mekaniklerini oluşturmuştuk.Bu ders başladığımız tank projemizin ikinci ve son bölümü olacaktır.İlk yazıya bakmadıysanız, tavsiyem önce o yazıyı okuyup daha sonra bu yazıya bakmanızdır.Çünkü tek bir yazıyı uzatmamak adına iki parçaya ayırdım ve birbirlerinini devamı niteliğindedirler.Şimdi ilk yazıda oluşturduğumuz C# Script dosyasından devam edeceğiz.O zaman vakit kaybetmeden başlayalım.
Çocuk ve Ebeveyn nesnelerin önemi.
Kodlarımızı yazmaya başlamadan önce, yazımızın ilk bölümünde tank modelimizi üç parçaya ayırmıştık ve kullanacağımız mekaniklere göre bu parçaları sınıflandırmıştık.Bu mekaniklerden biride ateş etme üzerineydi.Bildiğiniz gibi, tankımızdan çıkacak olan topun bir referans pozisyonuna sahip olması gerekiyor.Aksi takdirde klonlanacak topumuzun oluşacağı bir koordinat olmayacaktır.
Bu referans için bir boş nesne oluşturmuştuk ve tankımızın ana silahının önüne konumlandırmıştık.Tabi bu Scene penceresinde yapmış olduğumuz işlemdi.Hierarchy penceresinde ise boş nesnemizi, bu ana silahımızın çocuk nesnesi olarak eklemiştik.Bu önemli bir noktadır.Çünkü faremiz ile tankımızın ana silahını belirlediğimiz açılarda hareket ettireceğiz.
Eğer topumuzun oluşmak için referans aldığı boş nesnemiz, ana silahımızın çocuğu konumunda olmazsa farklı bir koordinatta klonlanacaktır.Bu da istemediğimiz bir sonuç doğuracaktır.Kamera mekaniğimizi oluştururken de, yine aynı yolu izleyerek hiyerarşik bir düzenleme yapmıştık.Bunun sonuçlarını ilk derste görmemiştik.Ancak bu derste etkilerini hep birlikte göreceğiz.Şimdi hatırlatacak başka bir şey kalmadığını umarak kod yazmaya başlayabiliriz.
Kodlarımızı yazmaya başlayalım.
Evet şimdi, daha önce oluşturduğumuz C# Script dosyamızı editör yardımı ile açalım ve kodlarımızı yazmaya başlayalım.
public float MermiDolmaSuresi; public float YeniMermiSuresi; public float SilahinMinXSiniri; public float SilahinMaxXSiniri; public float FareHassasiyeti; private float FareninXEkseni; private float FareninYEkseni;
ilk olarak kullanacağımız değişkenleri oluşturmakla başlıyoruz.Burada bulunan tüm değişkenlerimizi float türünde oluşturuyoruz.İlk satırda, tankımızdan çıkacak mermimizin, ateşlenme aralığını kontrol etmek için bir değişken oluşturuyoruz.İkinci satırdaki değişkende, yeni mermimizin yüklenme süresini kontrol ediyoruz.Üçüncü satırdaki değişkende, tankımızda bulunan ana silahımızın, x eksenindeki minimum sınırını kontrol ediyoruz.
Dördüncü satırda ise, ana silahımızın x eksenindeki maksimum sınırını kontrol ediyoruz.Beşinci satırdaki değişkenimizde, faremizin hassasiyetini ayarlıyoruz.Burada belirlediğimiz değer ne kadar yüksek olursa, faremizin hızını o kadar artacaktır.Altıncı ve yedinci satırlarda oluşturduğumuz değişkenlerde ise, faremizin x ve y eksenlerinde ki koordinatlarını kontrol edeceğiz.
MermiDolmaSuresi = 5f; SilahinMinXSiniri = -5f; SilahinMaxXSiniri = 20f; FareHassasiyeti = 5f; FareninXEkseni = 0f; FareninYEkseni = 0f; Cursor.lockState = CursorLockMode.Locked;
Şimdi start() fonksiyonumuzun içerisine bu kodları yazıyoruz.İlk satırda değişkenimize, tankımızın ateş etme aralığını belirlemek için bir değer giriyoruz.Böylece tankımızdan istediğimiz aralıklarda ateş edebiliyoruz.İkinci satırda tankımızın ana silahının, minimum x ekseni için bir değer belirliyoruz.Üçüncü satırda ise, ana silahımızın x eksenindeki maksimum açısı için bir değer belirliyoruz.
Burada bir eksi “-” ve bir artı “+” değer kullandık.Eksi olan değişkenimiz, silahımızın aşağıya doğru ne kadar inebileceğini kontrol ederken, artı olan değişken ise, silahımızın yukarıya doğru ne kadar çıkabileceğini kontrol ediyor.Dördüncü satırda, faremizin hassasiyeti için oluşturduğumuz değişkene bir değer belirliyoruz.Demin bahsetmiştim ancak yine söylemekte yarar var.
Bu değişkenimize verdiğimiz değer ne kadar yüksek tutarsanız o kadar hızlı bir fare hareketimiz olur.Bu projede kontrol ettiğimiz nesne bir tank olduğu içinde, daha gerçekçi bir sonuç için bu değeri olabildiğince düşük tutmanızda yarar vardır.Beşinci ve altıncı satırlardaki değişkenlerin değerini sınır “0” yapıyoruz.Çünkü bu değişkenlerin değerini sürekli değişkenlik gösterecektir.Son satırda ise, faremizin imlecini kilitliyoruz.Böylece ekranda bir imleç gözükmeyecektir.
Ateş etme mekaniğinin oluşturulması.
void TankNisanAlma () { FareninYEkseni += Input.GetAxis ("Mouse X") * FareHassasiyeti * Time.deltaTime; FareninXEkseni += Input.GetAxis ("Mouse Y") * FareHassasiyeti * Time.deltaTime; FareninXEkseni = Mathf.Clamp (FareninXEkseni, SilahinMinXSiniri, SilahinMaxXSiniri); TankAnaSilahi.transform.localRotation = Quaternion.Euler (-FareninXEkseni, 0, 0); TankKulesi.transform.localRotation = Quaternion.Euler (0, FareninYEkseni, 0); }
Evet şimdi yeni bir fonksiyon oluşturuyoruz.Bu fonksiyonda ateş etme ve nişan alma mekaniklerini oluşturacağız.İlk satırda faremizin y ekseni için oluşturduğumuz değişkene, faremizin x eksenindeki koordinatlarını eşitliyoruz.Tabi bunu yaparken, faremizin hızını kontrol eden değişken ile her karede aynı hızı sağlamak için Time.deltaTime fonksiyonunu çarpıyoruz.İkinci satırda ise bu sefer faremizin x ekseni için oluşturmuş olduğumuz değişkenimize, faremizin y eksenindeki koordinatları eşitliyoruz.
Yine burada da, Time.deltaTime fonksiyonumuzu ve faremizin hızı için oluşturduğumuz değişkenimizi ekliyoruz.Üçüncü satırda, faremizin ilgili eksen üzerinde ki hareketini sınırlandırıyoruz.Bunun için, minimum ve maksimum hareketi kontrol eden Mathf.Clamp fonksiyonunu kullanıyoruz.Böylece faremizi iki değer arasında sınırlandırıyoruz.Dördüncü satırda, değişkenimize tanımladığımız tankımızın ana silahının, x eksenindeki açısını kontrol ediyoruz.
Bunu yaparken rotation değilde, localRotation fonksiyonunu kullanıyoruz.Çünkü ilgili nesnemizin bir ebeveyn nesnesi bulunuyor ve yerel konumu bu nesneden etkileniyor.Bunun için bu fonksiyonu kullanıyoruz.Geri dönecek olursak, ana silahımız için faremizi sadece x ekseninde kullanıyoruz.Böylece fare hareketimiz sadece bu eksen ile sınırlı kalıyor.Beşinci satırda ise, takımızın kafa yani kule kısmını kontrol ettiğimiz değişkende, faremizi sadece y ekseninde kullanıyoruz.Bir önceki satırda ki aynı mantık burada da geçerlidir.
YeniMermiSuresi += Time.deltaTime; if (Input.GetMouseButtonDown (0)) { if (YeniMermiSuresi > MermiDolmaSuresi) { GameObject yeniTop = Instantiate (TankTopu, TankTopuCikisi.transform.position, TankTopuCikisi.transform.rotation) as GameObject; yeniTop.GetComponent<Rigidbody> ().AddRelativeForce (Vector3.forward * 5000); Destroy (yeniTop, 5f); TankTepmeKuvveti (KuvvetUygula: true); YeniMermiSuresi = 0; } } TankTepmeKuvveti (KuvvetUygula: false);
Bu kısma kadar tankımızın nişan alma mekaniğini tamamladık.Şimdi ise ateş etme mekaniğini oluşturacağız.Bunun için bu kodları fonksiyonumuzun devamına yazıyoruz.İlk satırda, yeni mermimizin dolma süresi için oluşturduğumuz değişkenin değerini Time.deltaTime fonksiyonu ile artyırıyoruz.İkinci satırda yeni bir koşul oluşturuyoruz.Eğer faremizin sol tuşuna basılırsa, koşulumuz doğru oluyor.Üçüncü satırda yine bir koşul oluşturuyoruz.
Burada ise eğer yeni mermimizin dolma süreci, belirlediğimiz maksimum dolma süresinden büyükse, koşulumuz doğru oluyor.Yani mermimizi ateşleyebiliyoruz.Dördüncü satırda, bir GameObject değişkeni oluşturuyoruz.Bu değişkene, Instantiate fonksiyonu yardımı ile yeni mermi klonları oluşturuyor ve tanımlıyoruz.Yazımım başında belirtiğim gibi, mermimizin klonlanacağı referans değer için “TankTopuCikisi” değişkenimizi kullanıyoruz.
Beşinci satırda klon mermimizin Rigidbody bileşenine erişerek, AddRelativeForce fonksiyonu ile belirli yönde ve hızda bir kuvvet uyguluyoruz.Altıncı satırda Destroy fonksiyonunu kullanarak, mermimizi oluştuktan belirli bir süre sonra sahnemizden siliyoruz.Yedinci satırı birazdan anlatacağım.Sekizinci satırda ise, bir sonraki mermimizin doldurulması için gereken süreyi sıfırlıyoruz.Son satıra, yedinci satırda olduğu gibi birazdan değineceğim.
Mermi tepme mekaniğinin oluşturulması.
Evet nişan alma ve ateş etme mekaniklerimizi de tamamladık.Şimdi tankımız ile ateş etme sırasında oluşan tepme kuvveti için yeni bir fonksiyon oluşturuyoruz.
void TankTepmeKuvveti (bool KuvvetUygula) { if (KuvvetUygula) { Vector3 MaxTankHareketi = new Vector3(Random.Range(transform.localPosition.x, 0.5f),transform.localPosition.y, Random.Range(transform.localPosition.z, 0.5f)); transform.localPosition = Vector3.Slerp(transform.localPosition, MaxTankHareketi, 1 * Time.deltaTime); Quaternion MaxTankTepmesi = Quaternion.Euler (Random.Range (transform.localRotation.x, 20), Random.Range (transform.localRotation.y, 20), Random.Range (transform.localRotation.z, 20)); transform.localRotation = Quaternion.Slerp (transform.localRotation, MaxTankTepmesi, 1 * Time.deltaTime); } }
Bu fonksiyonda bir adet bool türünde parametre kullanacağız.Bu parametre ile koşulumuzun true yada false durumlarında yapması gerekenleri planlayacağız.İkinci satırda yeni bir koşul oluşturuyoruz.Eğer parametredeki koşulumuzun değeri true ise içerisinde bulunan kodları çalıştırıyoruz.Üçüncü satırda, bir Vector3 değişkeni oluşturuyoruz.Bu değişkende belirlediğimiz eksenlerde rastgele pozisyon değerleri oluşturacağız.
Bunun sonucunda da, ateşleme gerçekleştiği zaman tankımızda bir tepme kuvveti oluşacak.Tabi bu rastgele değer için Random fonksiyonundan yararlanıyoruz.Dördüncü satırda, Vector3 değişkenimizde belirlediğimiz bu yeni koordinatları tankımızın o anki değerleri ile değiştiriyoruz.Bu geçişin keskin olmaması için, Slerp fonksiyonunu kullanıyoruz.Beşinci satırda ise, Quaternion değişkeni oluşturuyoruz.
Bu sefer tankımıza belirlediğimiz eksenlerde rastgele açı değerleri veriyoruz.Altıncı satırda yeni rotation değerlerini, tankımızın rotation değerleri ile değiştiriyoruz.Yine geçişin düzgün ve pürüzsüz olması için, Slerp fonksiyonundan yararlanıyoruz.
else { if (YeniMermiSuresi < MermiDolmaSuresi) { Vector3 MinTankHareketi = new Vector3 (Random.Range (0, transform.localPosition.x), transform.localPosition.y, Random.Range (0, transform.localPosition.z)); transform.localPosition = Vector3.Slerp (transform.localPosition, MinTankHareketi, 1 * Time.deltaTime * 5); Quaternion MinTankTepmesi = Quaternion.Euler (Random.Range (0, transform.localRotation.x), Random.Range (0, transform.localRotation.y), Random.Range (0, transform.localRotation.z)); transform.localRotation = Quaternion.Slerp (transform.localRotation, MinTankTepmesi, 1 * Time.deltaTime * 5); } }
Faremizin sol tuşuna bastık ve mermimizi ateşledik.Bunun sonucunda tankımızda bir tepme kuvveti oluştu.Şimdi tankımızın eski pozisyonuna ve rotasyonuna geri gelmesi gerekiyor.Aksi halde yapay bir görüntü oluşacaktır.İşte bunun için koşulumuza bir else ekliyoruz.Bu blokta oluşacak kodlarımızı incelemeye başlayalım.İkinci satırda yeni bir koşul oluşturuyoruz.
Eğer yeni mermimizin dolma süreci, belirlediğimiz maksimum dolma süresinden küçükse, koşulumuz doğru oluyor.Üçüncü satırda, Vector3 değişkeni oluşturuyoruz.Bu sefer Random fonksiyonunda aldığı değerleri, sıfır “0” ile ilgili eksendeki kendi değeri arasında belirliyoruz.Dördüncü satırda bu yeni position değerlerini, Slerp fonksiyonu ile tankımızın eski position değerlerine eşitliyoruz.
Beşinci satırda değişkenimizin Random fonksiyonunda aldığı değerleri, sıfır “0” ile ilgili eksendeki kendi rotation değerleri arasında belirliyoruz.Altıncı satırda ise, bu yeni rotation değerlerini yine aynı Slerp fonksiyon ile tank nesnemize uyguluyoruz.
TankTepmeKuvveti (KuvvetUygula: true); TankTepmeKuvveti (KuvvetUygula: false);
Şimdi bu kodlarımıza geri dönelim.Yazarken anlatmadım çünkü fonksiyonlarımız tamamlanmamıştı.Artık tamamlandığına göre mantığını daha iyi kavrayabilirsiniz.Birinci ve ikinci satırdaki kodlar ile yazdığımız fonksiyonu çağırıyoruz.Burada nerede bu fonksiyonu çağırdığımız önem arz ediyor.Zira aldığı parametrenin değerlerine bakacak olursak, birinde true değeri tanımlıyken, değerinde false değeri tanımlı gözüküyor.
İşte bu noktada değeri true olan fonksiyonumuzu, tankımızdan ateş ettiğimiz zaman kullanıyoruz.Ve böylece bir tepme kuvveti oluşturuyoruz.False değerine sahip olan fonksiyonumuzu ise, ateşleme koşulumuzun dışında kullanıyoruz.Böylece tepme kuvveti ile sarsılan tankımız eski pozisyonuna geri geliyor.
TankNisanAlma ();
Son olarak update() fonksiyonumuzun içerisine bu kodu yazıyoruz.Böylece tankımızın nişan alma, ateşleme ve tepme mekanikleri çalışmaya hazır oluyor.Evet böylece kodlarımızın ikinci kısmını tamamlamış oluyoruz.İkinci kısmımızı da hazırladık.
Evet projemiz tamamlanmış bulunuyor.Bu şekilde birçok mekanik oluşturabilirsiniz.Örneğin, ateş edildikten sonra çıkan dumanı ben eklemedim.Siz ekleyebilir ve daha da geliştirebilirsiniz.Bir sonraki yazıda görüşmek üzere…