Evet yeni bir bölüme daha başlıyoruz. Bu bölümde düşman karakterlerimize health bar yani can barı ekleyeceğiz ve isabet eden mermilere göre canlarını azaltacağız ama sadece bu kadarla yetinmeyeceğiz tabi ki. Can barı haricinden ekleyeceğimiz diğer özellikleri de aşağıda sıralayalım.
- Düşman karakterlerine can barı ekleyeceğiz.
- 3 faklı kulemizden çıkan mermilere göre azalacak enerjiler için bir sistem yapacağız.
- 3 kulemiz olan mancınık kulemiz için splash vurma özelliği yani aynı anda birden fazla düşmana vurma özelliği ekleyeceğiz.
O zaman başlayalım…
Can Barı Ekleme İşlemi
İlk olarak sahnemizde yer alacak tüm düşman karakterlerine can barı eklemesi yapacağız ve tabi ki bu işlemi yapmak için UI objelerini kullanacağız.
UI ile ilgili dersleri incelemediyseniz buyurun Unity UI Dersleri
“BirinciDusman” yani mavi düşman karakterimizin prefab dosyasını açalım ve içerisine “UI/Image” seçeneği ile bir “Image” objesi ekleyelim. “Image” eklemesinden sonra otomatik olarak “Canvas” objesi de eklenmiş olacaktır. Burada yer alan “Image” objesinin ismini “SaglikArkaPlan” olarak değiştirebilirsiniz.
Eklediğimiz “Image” objesini yani “SaglikArkaPlan” objesini seçerek içerisine bir adet daha ” UI/Image” objesi ekleyelim. Bunun da ismini “SaglikBari” olarak değiştireceğiz. Sonuç olarak aşağıdaki hiyerarşide gözükmesi gerekiyor.
Önce ana canvas objemizin “inspector” ayalarını aşağıdaki gibi yapalım. Burada “Canvas” bileşenin “Render Mode” ayarını “World Space” olarak değiştirdik. Bu değişiklik sonrasında “Rect Transform” kısmı değişiklik yapabilmemiz için aktif duruma gelecektir. Sonrasında ise “Scale” bölümünü küçülterek ekrandaki gibi bir boyda olmasını sağlayacağız. Burada pozisyon bilgilerini karakterin biraz üstünde olacak şekilde düzenlemeyi unutmayın.
Şimdi “Canvas” objesinin alt objesi olan “SaglikArkaPlan” isimli “Image” objesinin ayarlarını yapacağız. Bu kısımda sadece “Color” kısmına yeni bir renk seçiyoruz. Seçeceğimiz renk enerjimiz azaldığında ya da bittiğinde gözükecek olan renk anlamına geliyor. Siyah rengi seçerek saydamlık değerini 134 olarak değiştirdim.
Son aşama olarak ise “SaglikBari” objemizin “Source Image” alanına bir sprite ekleyeceğiz ve “Image Type” alanında değişiklikler yapacağız. Ama önce 2x2px boyutunda beyaz renkte bir jpg dosyası bulmanız gerekiyor. Photostop ya da internette rahatça bulabilirsiniz ama isterseniz aşağıdan da indirebilirsiniz.
Resim dosyasını “Assets” klasörü altında “Sprites” isimli bir klasör oluşturarak içerisine atın. Atma işleminden sonra görselimize tıklayarak “Inspector” alanından “Texture Type” alanını “Sprite (2D and UI)” ve “Filter Mode” ayarını “Point(No filter)” olarak değiştirdikten sonra “Apply” butonuna basarak sprite türüne çevirin.
Şimdi “SaglikBari” objemize tıklayarak ayarlarını aşağıdaki gibi yapın. Burada “Source Image” kısmından eklediğimiz 2×2 boyutundaki sprite dosyamızı seçeceğiz. Peki buraya neden bir görsel ekledik diye düşünebilirsiniz.
Şöyle düşünün karakter darbe aldıkça bizim bu yeşil kısmı azaltmamız gerekiyor. Bunun için sprite dosyasının yeşil rengini kademe kademe azaltmamız lazım. Bunu her kademe için farklı sprite dosyaları kullanarak yapabiliriz tabi ama oldukça uğraştırıcı olacaktır. “Filled” özelliği kullanarak bu işlemi yapabiliriz ve eklediğimiz beyaz sprite dosyası, yeşil rengi ezmediği için yani baskın bir renk olmadığı için uygun bir seçim oluyor.
“Image Type” alanında seçtiğimiz “Filled” ve aşağıdaki ayarlar bize karakterin canı azaldıkça hangi tarafa doğru yeşil rengin azalacağı ve bu işlemin yatay pozisyonda olacağı gibi özellikleri sağlıyor.
- Fill Method: Horizontal
- Fill Origin: Left
- Fill Amount: 1
Aslında aşağıdaki görsele bakarsanız “Fill Amount” ayarını ve amacımızı daha iyi anlarsınız. Kod tarafında karakterimize her mermi isabet ettiğinde bu ayarı azaltacağız.
Can barını tüm düşman karakterleri için ayarladık sonra, şimdi sıra geldi biraz kodlara girmeye…
Can Barı için C# Kodları
Düşman karakterlerinin can barlarının azaltabilmeleri için merminin temas ettiğini tespit edip. Tespit etme işleminden sonra ise ne kadar canlarının gideceğini belirlememiz gerekecek. Bu yüzden C# script dosyalarımızın bazı kısımlarına müdahale etmemiz ve hasar işlemini hesaplamaları yapmamız için eni bir sınıfa yani C# script dosyasına ihtiyacımız var. İleride ekleyeceğimiz animasyon kontrollerini de yeni dosyamız üzerinden yapacağız.
Can Barı Hesaplamaları İçin Script Dosyası
“Dusmanlar” isminde yeni bir C# script dosyası oluşturarak tüm düşman prefab dosyalarına atıyoruz ve script dosyamızın içerisine aşağıdaki kodları ekliyoruz.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Dusmanlar : MonoBehaviour { [SerializeField]public float baslangicSaglik = 100; [SerializeField]public float saglik; [SerializeField]public Image saglikBari; [SerializeField]public GameObject saglikCanvas; void Start() { saglik = baslangicSaglik; if (!saglikCanvas.activeSelf) saglikCanvas.SetActive(true); } public void HasarAlma(float miktar) { saglik -= miktar; saglikBari.fillAmount = saglik / baslangicSaglik; if (saglik <= 0) DusmaniOldur(); } void DusmaniOldur() { Destroy(gameObject); } }
Şimdi neler yaptık biraz bakalım.
- “baslangicSaglik“, “saglik” isminde iki adet “Float” değişken oluşurduk. Bu değişkenlerden “baslangicSaglik” yani düşman karakterinin toplamdaki sağlık miktarını girdik. İkinci değişkenimiz “saglik” üzerinde ise darbe aldığında ne kadar canı kaldığını tutacağız.
- “saglikBari” ve “saglikCanvas” değişkenlerimizi ise dışarıdan atama yapmak için ekledik. Bu değişkenler üzerinden can barını gizleme ve hasar aldığında “Fill Amount” değerine erişmek için kullanacağız.
- Sonradan “Kule” script dosyası üzerinden çağıracağımız “HasarAlma” isminde bir fonksiyon oluşturduk. Bu fonksiyon “Float” türünde “miktar” isminde bir parametre alacak. Fonksiyona gelen miktar kadar sağlığı azaltmak için
saglik -= miktar;
yazdık. - “Fill Amount” değerini azaltmak için
saglikBari.fillAmount = saglik / baslangicSaglik;
kodunu ekledik. Burada “saglik / baslangicSaglik” yazmamızın sebebini Fill Amount” özelliğinin sadece 0-1 arasında değer alması. if (saglik <= 0) DusmaniOldur();
kısmında ise düşman karakterinin canı 0 ya da daha az olduğunda “DusmaniOldur” isminde bir fonksiyon çağırmasını istedik.- “DusmaniOldur” fonksiyonu içerisine eklediğimiz
Destroy(gameObject);
ile düşman karakterimizi öldürdüğümüzde sahneden yok etmek için kullandık.
“Kule” Script Dosyasına Yeni Parametreler Ekleme
“Kule” script dosyası üzerinden tespit ettiğimiz hedefleri gönderirken iki yeni parametre daha göndereceğiz. Bu parametreler için önce iki adet değişken oluşturalım.
[SerializeField] private float hasarGücü; [SerializeField] private float cokluVurma;
Sonrasında ise “AtisYap” metodunun içerisinde çağırdığımız “HedefiAta” metodunda aşağıdaki değişikliği yapalım.
mermi.HedefiAta(hedef, hasarGücü, cokluVurma);
Böylece “Mermi” sınıfındaki metodumuza yeni iki adet parametre göndermiş olduk. “hasarGucu” ile merminin ne kadar hasar vereceğini ve “cokluVurma” ile ne kadar bir yarıçaptaki diğer düşmanları da vurmasını istediğimizi belirttik.
“coklu” vurma mancınık kulemiz için kullanacağız. Yani buraya atadığımız yarı çap bilgisi kadar merminin düştüğü alandaki diğer düşmanlarda hasar alacak. İlerleyen adımlarda daha detaylı değineceğim.
Tabi her kulemiz için aşağıdaki gibi değerleri girmeyi unutmayın.
Tüm kuleler için girdiğim değerler aşağıdaki gibi.
Birinci Kule
Atış Mesafesi: 2.5
Atış Hızı: 1
Hasar Gücü: 20
Çoklu Vurma: 0
İkinci Kule
Atış Mesafesi: 8
Atış Hızı: 0.25
Hasar Gücü: 30
Çoklu Vurma: 0
Üçüncü Kule
Atış Mesafesi: 4.5
Atış Hızı: 0.25
Hasar Gücü: 40
Çoklu Vurma: 1
Ek olarak tüm mermi scrptinin içeriğini ekliyorum ki atladığım bir kısım varsa ya da hata alıyorsanız kontrol edebilirsiniz.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Kule : MonoBehaviour { [Header("Kule Özellikleri")] [SerializeField] private float atisMesafesi = 3f; [SerializeField] private float atisHizi = 0.5f; [SerializeField] private float hasarGücü; [SerializeField] private float cokluVurma; [Header("Kule Yapılandırması")] [SerializeField] private Transform donecekKisim; [SerializeField] private GameObject mermiSablonu; [SerializeField] private Transform[] atisNoktasi; [SerializeField] private GameObject kuleEfekti; [SerializeField] private Transform mancinikHareketi; [SerializeField] private Animator animator; private float atisYenilemeHizi = 0f; private Transform hedef; private GameObject olusturulanMermi; private void Start() { InvokeRepeating("DusmaniBul", 0f, 0.5f); } private void Update() { if (hedef == null) return; if (transform.name == "KureBirinci(Clone)") { Quaternion hedefeBak = Quaternion.LookRotation(hedef.position - transform.position); donecekKisim.rotation = Quaternion.Euler(0f, hedefeBak.eulerAngles.y, 0f); } else if (transform.name == "KuleIkinci(Clone)") { Quaternion hedefeBak = Quaternion.LookRotation(hedef.position - transform.position); donecekKisim.rotation = Quaternion.Euler(-70f, hedefeBak.eulerAngles.y, 0f); } else if (transform.name == "KuleUcuncu(Clone)") { Quaternion hedefeBak = Quaternion.LookRotation(hedef.position - transform.position); donecekKisim.rotation = Quaternion.Lerp(donecekKisim.rotation, Quaternion.Euler(0f, hedefeBak.eulerAngles.y, 0f), 300f * Time.deltaTime); } if (atisYenilemeHizi <= 0f) { if (gameObject.CompareTag("Mancinik")) { animator.SetBool("BombaYenileme", false); while (mancinikHareketi.eulerAngles.x <= 44 && !animator.GetBool("BombaYenileme")) { mancinikHareketi.rotation = Quaternion.Lerp(mancinikHareketi.rotation, Quaternion.Euler(45f, donecekKisim.eulerAngles.y, donecekKisim.eulerAngles.z), 0.1f); } } AtisYap(); atisYenilemeHizi = 1f / atisHizi; } atisYenilemeHizi -= Time.deltaTime; } public void AtisYap() { for (int i = 0; i < atisNoktasi.Length; i++) { if (mermiSablonu.CompareTag("MancinikBomba")) { olusturulanMermi = Instantiate(mermiSablonu, atisNoktasi[i].position, atisNoktasi[i].rotation); StartCoroutine(MancinikHareketiYap()); } else { olusturulanMermi = Instantiate(mermiSablonu, atisNoktasi[i].position, atisNoktasi[i].rotation); } if (olusturulanMermi != null) { Mermi mermi = olusturulanMermi.GetComponent<Mermi>(); if (mermi != null) { mermi.HedefiAta(hedef, hasarGücü, cokluVurma); } } if (kuleEfekti != null) { GameObject olusanKuleEfekti = Instantiate(kuleEfekti, atisNoktasi[i].position, atisNoktasi[i].rotation); } } } public IEnumerator MancinikHareketiYap() { while (mancinikHareketi.eulerAngles.x > 0.2) { mancinikHareketi.rotation = Quaternion.Lerp(mancinikHareketi.rotation, Quaternion.Euler(0f, donecekKisim.eulerAngles.y, donecekKisim.eulerAngles.z), 0.1f); yield return null; } animator.SetBool("BombaYenileme", true); } void DusmaniBul() { string[] dusmanEtiketleri = { "KirmiziDusman", "MaviDusman" }; List<GameObject> dusmanlar = new List<GameObject>(); for (int i = 0; i < dusmanEtiketleri.Length; i++) { GameObject[] dusmanlarArray = GameObject.FindGameObjectsWithTag(dusmanEtiketleri[i]); for (int ii = 0; ii < dusmanlarArray.Length; ii++) { dusmanlar.Add(dusmanlarArray[ii]); } } float enKisaMesafe = Mathf.Infinity; GameObject enYakinDusman = null; foreach (var dusman in dusmanlar) { float dusmanaUzaklik = Vector3.Distance(transform.position, dusman.transform.position); if (dusmanaUzaklik < enKisaMesafe) { enKisaMesafe = dusmanaUzaklik; enYakinDusman = dusman; } } if (enYakinDusman != null && enKisaMesafe <= atisMesafesi) { hedef = enYakinDusman.transform; } else hedef = null; } void OnDrawGizmosSelected() { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, atisMesafesi); } }
“Mermi” Script Dosyasından “Dusmanlar” Script Dosyasına Erişme
Hedefimiz hasar aldığında canının azalmasını istediğimiz için en uygun yer “Mermi” script dosyası olacaktır. Merminin hedefe isabet ettiğini buradaki “OnTriggerEnter” ile tespit ediyorduk. “OnTriggerEnter” fonksiyonu ile merminin isabet ettiğini tespit ederek “Dusmanlar” script içerisindeki “HasarAlma” fonksiyonuna bildirebiliriz.
Aşağıda “Mermi” script dosyasının tamamını gönderiyorum. Eklenen değişiklikleri işaretledim.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Mermi : MonoBehaviour { [SerializeField] private GameObject hedefVurmaEfekti; [SerializeField] private Rigidbody fizik; [SerializeField] private float guc; private bool balistikYukselis; private bool birKez; private Transform hedef; private float hasarGücü; private float cokluVurma; private float hiz = 7f; private Dusmanlar dusmanlar; public void HedefiAta(Transform _hedef, float _hasarGucu, float _cokluVurma) { hedef = _hedef; hasarGücü = _hasarGucu; cokluVurma = _cokluVurma; } private void FixedUpdate() { if (hedef != null) { if (gameObject.CompareTag("SavunmaFuze")) { Vector3 fark = hedef.position + new Vector3(0, .4f, 0) - transform.position; float kareMesafe = hiz * Time.deltaTime; transform.Translate(fark.normalized * kareMesafe, Space.World); } else if (gameObject.CompareTag("BalistikFuze")) { if (transform.position.y < 6 && balistikYukselis == false) { fizik.AddForce(Vector3.up * guc * Time.deltaTime * hiz); } else { Quaternion balistik = Quaternion.LookRotation(hedef.position - transform.position); fizik.MoveRotation(Quaternion.RotateTowards(transform.rotation, balistik, 10)); fizik.velocity = transform.forward * Time.deltaTime * guc; balistikYukselis = true; } } else if (gameObject.CompareTag("MancinikBomba")) { if (!birKez) { fizik.velocity = VelocityHesapla(hedef.position, transform.position, 1f); birKez = true; } } } } void Update() { if (hedef == null) { Destroy(gameObject); return; } } Vector3 VelocityHesapla(Vector3 hedef, Vector3 baslangic, float sure) { Vector3 fark = hedef - baslangic; Vector3 fark_x_z = fark; fark_x_z.Normalize(); fark_x_z.y = 0f; float Sy = fark.y; float Sxz = fark.magnitude; float Vxz = Sxz / sure; float Vy = Sy / sure + 0.5f * Mathf.Abs(Physics.gravity.y) * sure; var sonuc = fark_x_z * Vxz; sonuc.y = Vy; return sonuc; } private void OnTriggerEnter(Collider other) { if (other.gameObject.layer == 10 || other.gameObject.layer == 9) { GameObject hedefOlusanEfekt = Instantiate(hedefVurmaEfekti, transform.position + new Vector3(0, .5f, 0), transform.rotation); Destroy(hedefOlusanEfekt, 1f); if (gameObject.CompareTag("MancinikBomba")) { Collider[] hasarAlacakHedefler = Physics.OverlapSphere(transform.position, cokluVurma); foreach (var hedef in hasarAlacakHedefler) { if (hedef.gameObject.layer == 10) { Debug.Log("Hasaralan hedef: " + hedef.name); hedef.transform.GetComponent<Dusmanlar>().HasarAlma(hasarGücü); } Destroy(gameObject); } } else { dusmanlar = other.transform.GetComponent<Dusmanlar>().dusmanlar.HasarAlma(hasarGücü); } Destroy(gameObject); } } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireSphere (transform.position, 1f); } }
private Dusmanlar dusmanlar;
yazarak “Dusmanlar” sınıfından bir obje oluşturduk. Bu obje üzerinden yeni eklediğimiz “Dusmanlar” script dosyasına erişeceğiz.
Fakat önce düşman karakterinin içinde yer alan “Dusmanlar” scriptine erişmemiz gerekiyor. Bunun için dusmanlar = other.transform.GetComponent<Dusmanlar>();
yazarak obje üzerinden bileşen olarak eklenmiş scriptimizi yakalıyoruz.
Sonrasında ise ” OnTriggerEnter ” metodu içerisinde dusmanlar.HasarAlma(hasarGücü);
yazarak “dusmanlar” değişkeni üzerinden “HasarAlma” fonksiyonuna erişerek azaltmak istediğimiz sağlık miktarını gönderiyoruz.
“HedefiAta” üzerinden “Kule” scripti ile bize gelen en yakın hedef bilgisini tutuyorduk. Bu alana iki adet yeni parametre ekledik. Böylece “Kule” scripti üzerinden kulenin hasar vurma gücünü ve birden fazla düşmana aynı anda vurup vuramayacağı bilgisini alabileceğiz. Gelen parametreleri yeni eklediğimiz “hasarGücü” ve “cokluVurma” değişkenlerine atadık.
Splash Vurma Özelliği Ekleme
“OnTriggerEnter” üzerinden objelerin temaslarını tespit edebiliyorduk. Aslında burada kontrol etmemiz gereken iki adet mermi türümüz var. Birinci ve ikinci kulelerimizin mermileri hedefe vardığında standart olarak canlarını azaltacaktır. Fakat biraz fantezi olması için üçüncü kulemiz olan mancınık kulesi için “Splash” yani aynı anda birden fazla düşmana vurma özelliği ekledim.
Bu sebeple if (gameObject.CompareTag("MancinikBomba"))
yazarak mancınık kulemizin mermilerini etiketi üzerinden yakalayarak ayrı hesaplayacağız.
Collider[] hasarAlacakHedefler
yazarak Collider türünde eleman alacak bir array oluşturduk. Bu array içerisine ise “Physics.OverlapSphere” metodu sayesinde yakaladığımız objeleri ekletiyoruz.
Buradaki mantık Physics.OverlapSphere(transform.position, cokluVurma);
ile belirlediğimiz “transform.position” yani merminin pozisyonundan belirli bir alana kadar olan diğer objeleri tespit etmemize yaraması. Böylelikle belirlediğimiz yarıçapta olan tüm objeleri “hasarAlacakHedefler” array içerisine ekleyerek foreach (var hedef in hasarAlacakHedefler) ile array içerisinde tek tek geziyoruz.
Array elemanlarının her birinde hedef.transform.GetComponent<Dusmanlar>().HasarAlma(hasarGücü);
yazarak önce üzerinde olan “Dusmanlar” scriptine erişiyoruz ve içerisinde yer alan “HasarAlma” metodunu çağırıyoruz.
Bug: Biliyorsunuz sizden daha önce yapıp sonra içeriğe dökmeye çalışıyorum. Bu alanda bize gelen hedef bilgisi olup olmadığını kontrol etmediğimizi fark ettim. Bu yüzden if (hedef != null)
yazarak hedefi sürekli kontrol etmiş oluyoruz. Eğer bu düzenlemeyi yapmasaydık ileride bir düşman karakteri öldüğünde aynı hedefe giden başka bir mermi varsa hata alacaktık.
Son olarak düşman karakterimize eklediğimiz “Dusmanlar” script dosyasına yapacağımız dışarıdan atamaları yapmayı unutmayın.
Eğer atladığınız bir kısım yoksa aşağıdaki sonucu elde etmiş olmanız gerekiyor.
Oyun yapmak istiyorum
Bunun için buradayız. Sende aramıza katıl 🙂