Birlikte öğreniyoruz...

Evet arkadaşlar artık bir oyun yapma tecrübesi elde etmemizin zamanı geldi. Sadece öğrendiğimiz konuları içeren bir eğitim olmayacak, bazı bölümlerde yeni konular öğreneceğiz. Ama merak etmeyin daha önceden olduğu gibi her detaya detaylı bir şekilde değineceğiz.

Tower Defence Oyunu Yapıyoruz!” konusunda biraz yapacaklarımızı anlatmaya çalıştım. Konuya göz atarak oyunun mantığını biraz inceleyerek bu derse başlamanızda fayda olacaktır. İlk defa beraber yapacağımız oyunumuzda, konuları kademe kademe ilerleyeceğiz. Böylece hiçbir şeyi atlamadan sindire sindire öğrenmiş olacağız. Bu konumuzda yani ilk bölümümüzde ise oyunun sahne tasarımı kısmını yapacağız.

“Tower Defence” yani “Kule Savunma” oyunlarındaki mantık bellidir. Düşman ya da düşmen toplulukları A noktasından yolsa çıkarak B noktasına ulaşmaya çalışırlar ve bizde B noktasına ulaşmadan engellemeye çalışırız.

Basit bir mantıkla bize düşmanın ilerleyeceği bir harita gerekiyor. Diğer “Tower Defence” oyun eğitimlerini incelediğimde genellikle küplerin belirli bir düzende dizilerek sahnenin tasarlandığını gözlemledim. Aslında mantıklı fakat değiştirmek istediğimizde ya da yeni bir level tasarımı yapmak istediğimiz de komple sahneyi baştan tasarlamamız gerekiyor.

Bende bir farklılık olması için dinamik bir yapı oluşturma kararı aldım. Yani biz sahnemizde istediğimiz özellikleri belirteceğiz ve oyun başladığında otomatik olarak sahnemiz oluşacak. Evet güzel bir fikir ve bunu yapmayı başardım. Fakat oyunun açılış süresini uzattığı için bu özelliği oyun başlamadan önce yapmanın daha mantıklı olacağını düşünerek yeni bir konuya yani “Editor Scripting” konusuna giriş yapmış olacağız.

Konuya giriş yapmadan önce dersin sonunda aşağıdaki videodaki gibi, dinamik sahne yapabileceğimiz bir yapı kurmayı öğrenmiş olacağız. O yüzden ellerinizi ovuşturmaya başlayabilirsiniz.

Tower Defence Oyunu Dinamik Sahne Tasarımı

Editor Scripting aslında çok geniş bir konu. Temel olarak Unity oyun motorunda olan pencereleri ve menüleri özelleştirmemize yarıyor diyebiliriz. Videoda fark ettiğiniz gibi Inspector penceresine C# Script dosyası bileşen olarak ekli ama ekstradan butonlar eklenmiş durumda. Arka planda bu butonlara yapmasını istediğimiz metotları bağladık ve böylece oyunu başlatmadan önce sahnemizi oluşturabildik.

Simdi bu bölümde ne yapacağız biraz yüzeysel olarak inceleyelim.

  • Zemini oluşturmak için bir kod yazacağız ve bu kodu bir butona ekleyerek, haritanın zeminini otomatik olarak oluşturulmasını sağlayacağız.
  • Yine aynı script ile harita üzerine belirlediğimiz sayıda engeller oluşmasını sağlayacağız. Haritanın rastgele konumlarında oluşacak bu engeller üzerine kule kurulamayacağı gibi düşmanlarda bu engeller üzerinden geçemeyecek.
  • Oyunun iki adet başlangıç noktası yani düşmanlarını çıkış noktaları da rastgele bir şekilde haritada konumlanacak. Başlangıç noktalarının, oyun alanını daraltması için sadece ilk satırda yer almasını sağlayacağız.
  • İki adet bitiş noktası da script üzerinden belirlediğimiz alanların arasında rastgele bir şekilde konumlanacak.

Temel mantık yukarıdaki gibi ama merak etmeyin şimdi detaya girmeye başlıyoruz.

Inspector Penceresini Özelleştirme

File / New Project ile yeni bir çalışma açarak temiz bir ekranda çalışmamıza başlayalım. Bir sonraki aşamada oluşturacağımız script ekleyebilmemiz için Hierarchy penceresi içinden Sağ tık / Create Empty yaparak boş bir obje oluşturalım ve ismini “OyunYoneticisi” olarak değiştirelim.

Şimdi düzenli gitmek için “Assets” penceresi içinde yeni bir klasör oluşturarak ismini “Scripts” yapın ve içerisine ilk C# script dosyamızı oluşturalım. Script ismini “HaritaOlusturma” olarak yapabilirsiniz. Oluşturduğumuz script dosyasını sürükleyerek ya da bileşen olarak “OyunYoneticisi” objesine ekleyelim.

Tower Defense Oyunu Sahne Tasarımı Başlangıcı
Tower Defence Oyunu Sahne Tasarımı Başlangıcı

Şimdi script dosyamızı açarak içerisine aşağıdaki gibi iki adet metot ekleyelim. Bu metotları harita oluşturma ve temizleme için kullanacağız fakat önce içlerine sadece bir log ekleyelim.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HaritaOlusturma : MonoBehaviour
{

    public void HaritayiOlustur()
    {
        Debug.Log("Haritayı Oluştur Butonuna Basıldı");
    }

    public void HaritayiTemizle()
    {
        Debug.Log("Haritayı Temizle Butonuna Basıldı");
    }

}

Kaydettikten sonra doğal olarak Inspector penceresinde herhangi bir değişiklik göremeyeceksiniz. Ama biz buraya iki adet buton eklemek ve bu butonlara tıkladığımız zaman az önce eklediğimiz metotları çalıştırmasını istiyoruz. İşte burada Editor Scripting konusu devreye giriyor.

Scripts klasörü ile aynı dizin içerisine bir adet “Editor” isminde bir klasör oluşturun ve içerisine ileride karıştırmamamız için “HaritaOlusturmaEditor” gibi benzer isminde bir C# Script dosyası ekleyelim.

Editor Scripting Giriş
Editor Scripting İçin C# Sciript Dostası Oluşturduk

Şimdi “HaritaOlusturmaEditor” script dosyamız açalım ve içerisine aşağıdaki kodları ekleyelim.

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(HaritaOlusturma))]
public class HaritaOlusturmaEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        HaritaOlusturma harita = (HaritaOlusturma) target;
        
        if (GUILayout.Button("Harita Oluştur"))
        {
            harita.HaritayiOlustur();
        }

        if (GUILayout.Button("Haritayı Temizle"))
        {
            harita.HaritayiTemizle();
        }
    }
}

Tabi sadece kodları eklemek hem benim hem de sizin için hiçbir şey ifade etmeyecektir. O yüzden her zaman olduğu gibi detaya girelim.

  • Fark ettiyseniz public class HaritaOlusturmaEditor : Editor satırının sonunda normalde “MonoBehaviour” yazardı ama bu sınıfı “Editor” sınıfından türetmemiz gerektiği için bu şekilde ekledik. Fakat bu sınıftan türetebilmemiz içinse using UnityEditor; yazarak UnityEdiyor Namespace’ ini eklememiz gerekiyor.
    Eğer namespace hakkında bilginiz yoksa daha önceden incelediğimiz Namespace nedir konusuna göz atabilirsiniz.
  • Unity metotları arasında olan public override void OnInspectorGUI() metodunu kullanmamız gerekiyor. Bu metot Inspector penceresini özelleştirmemiz için gerekli olan metot olmaktadır.
  • DrawDefaultInspector(); ise Unity Editor’un public metotlarından biri. Bu metodu sadece eklememiz yeterli. Inspector penceresinde olan varsayılan yapıyı ekliyor olarak düşünebilirsiniz.
  • HaritaOlusturma harita = (HaritaOlusturma) target; kısmında ise objeye ekli olan “HaritaOlusturma” bileşenine yani script dosyasına erişiyoruz. Burada kullanılan “target” kelimesi zorunlu olarak eklememiz gereken bir anahtar kelime.
  • GUILayout.Button("Harita Oluştur") yazarak butonumuzu oluşturmuş oluyoruz. Bu komut butona tıklandığında true değerini döndürmektedir. Eğer if bloğu içerisine koyarsanız, butona tıklandığında true döndürecektir. Yani butona tıklandığında içindeki komutlar çalıştır demiş oluyoruz. “Harita Oluştur” olarak yazdığımız ise buton üzerinde yazacak olan isimlerdir.
  • Yukarıda HaritaOlusturma harita = (HaritaOlusturma) target; yazarak “HaritaOlusturma” script dosyasına erişmiştik. Şimdi harita.HaritayiOlustur(); ya da harita.HaritayiTemizle(); yazarak bu script dosyasındaki metotları çağırmış olduk.

Not: Erişmek istediğiniz metotların erişim belirleyicilerinin “Public” olması gerekmektedir.

Eğer başarılı bir şekilde yaptıysanız “Inspector” penceresinde iki adet buton göreceksiniz. Bu butonlara tıkladığımız zaman ise “Console” ekranında metotlar içindeki çıktıları görebilirsiniz.

Inspector Penceresini Özelleştirme
Inspector Penceresine Buton Ekledik

Evet butonları oluşturduk peki şimdi “Harita Oluştur” butonuna tıklandığında nasıl otomatik olarak sahnemizin oluşturabileceğimizi inceleyelim.

Otomatik Sahne Tasarımı Oluşturma

Sırada butona tıkladığımızda otomatik olarak zemini oluşturma kısmı var. Bunun için bize neler gerekecek biraz kafa yoralım.

  • Zemini oluşturmamıza yarayacak obje.
  • Zemini otomatik olarak dizmeye başlayacağımız sahnede ki pozisyon bilgileri.
  • Haritanın boyutunu belirleme için en ve boy bilgileri.
  • Zemin objelerini ne kadar bir mesafede dizeceğimizin yani aralarındaki mesafenin oluşturulması için gerekli bilgiler.

Zemin Objesini Ayarlama

İlk ihtiyacımız zemini oluşturacak bir obje olacaktır. Bu objeyi prefab olarak kullanacağız. İstersek bir küp ekleyerek, prefab haline getirerek kullanabiliriz ya da kendimiz tasarlayabiliriz. Fakat ben burada hazır ve ücretsiz asset dosyalarından birini kullanacağım.

Bunun için aşağıda belirttiğim ve Asset Store üzerinden ücretsiz olarak indirebileceğiniz şablonu kullandım.

Meshtint Free Tile Map Mega Toon Series

İndirme ve import işlemlerini yaptıktan sonra “Assets” klasörünüzün altında “Meshtint Free Tile Map Mega Toon Series” isminde yeni bir klasör oluşacaktır. Bu klasörün altında yer alan “Prefabs” içindeki “Grass 01” isimli objeyi kendi “Prefabs” klasörümüzün içine atalım.

Ücretsiz asset dosyasını indirme
Ücretsiz Asset Dosyasını İndirdik

Tabi “Assets” içerisinde “Prefabs” klasörünüz yoksa oluşturduktan sonra atacağız. Sonrasında ise “Grass 01” ismini “Zemin” olarak değiştirelim.

tower defence zemin dosyasi
Zemin Dosyasını Hazırladık

Şimdi ise zemin dosyamızı “Harita Oluşturma” script dosyamıza ekleyeceğiz. Bunun için metotlarımın üst kısmına aşağıdaki kodu ekleyelim.

[SerializeField] private GameObject zeminSablonu;

SerializeField Nedir?

Burada ilk defa gördüğünüz bir durum olduğu için biraz detaya gireceğim. Fark ettiyseniz “zeminSablonu” “GameObject” türünde olan bir obje oluşturduk. Ayrıca objenin erişim belirleyicisi “private” durumda. Yani şuan “Inspector” penceresine baktığınızda bir değişikliğin olmaması gerekiyordu.

Fakat kontrol ettiğinizde aşağıdaki gibi alanın erişim belirleyicisi “public” verilmiş gibi dışarıdan obje atamaya uygun olduğunu göreceksiniz. Bunun sebebi başında eklenmiş olan [SerializeField] anahtar kelimesi.

Burada “SerializeField” kullanarak aslında şunu demiş oluyoruz. Bir objem var ve dışarıdan erişilebilir olmasın ama Unity editöründe yani bu durumda Inspector penceresinde görebileyim ve müdahale edebileyim. Yani bu şekilde erişimi kapatmak ve Unity üzerinden müdahale etmek istiyorsanız “SerializeField” kullanabilirsiniz.

SerializeField ve Private obje
SerializeField ve Private obje

Harita Yapısını Ayarlama

Haritamızı oluşturmak için yukarıda bahsettiğim bize bazı bilgiler gerekiyor. Bu bilgileri oyun başlamadan önce gireceğiz ve eklediğimiz buton ile haritayı oluşturabileceğiz.

O zaman bize gerekli olan aşağıdaki değişkenleri ekleyelim.

    [SerializeField] private float xBaslangic;
    [SerializeField] private float yBaslangic;
    [SerializeField] private int sutunBoyutu;
    [SerializeField] private int satirBoyutu;
    [Range(1, 2)] [SerializeField] private int xBosluk;
    [Range(1, 2)] [SerializeField] private int yBosluk;

xBaslangic: Zemin objelerinin başlayacağı x pozisyonu için eklenen değişken.
yBaslangic: Zemin objelerinin başlayacağı y pozisyonu için eklenen değişken.
sutunBoyutu: Haritanın y ekseni boyunca boyutunu belirlemek için eklenen değişken.
satirBoyutu: Haritanın y ekseni boyunca boyutunu belirlemek için eklenen değişken.
xBosluk: Zemin objeleri arasında, x ekseninde olacak boşluk bilgisi için eklenen değişken.
yBosluk: Zemin objeleri arasında, x ekseninde olacak boşluk bilgisi için eklenen değişken.

Range Nedir?

“SerializeField” kelimesinden sonra yeni olarak gördüğünüz “Range” üzerine duralım. “Range”, bir değişkenin alabileceği değer aralıklarının belirlenmesi için kullanılır. Yukarıdaki örneğimizde [Range(1, 2)] [SerializeField] private int xBosluk yazan kısımda belirtilen değerler arasında sayı verilebilir anlamına gelmektedir.

Tabi bizim ihtiyacımız olan “Integer” bir değer olduğu için sadece onu kullandık ama tabi ki aşağıdaki gibi belirleyerek aralığın “Float” olarak belirlenmesini sağlayabilirsiniz.

Range Kullanımı Örneği
Range Kullanımı Örneği

Harita İçin Algoritma Oluşturma

Elimizde haritamızı oluşturmak için gerekli olan değerleri alabileceğimiz objelerimiz var. Şimdi biraz matematiği devreye sokarak haritamızın istediğimiz şekilde oluşmasını sağlamamız gerekiyor.

Benim hazırladığım yapı aşağıdaki gibi bir for döngüsü içinde “Instantiate” metodunu kullanarak sahnede obje oluşturmak üzerine oldu.

public void HaritayiOlustur()
{
   for (int i = 0; i < sutunBoyutu * satirBoyutu; i++)
   {
   Instantiate(zeminSablonu, new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + -yBosluk * (i / sutunBoyutu)), Quaternion.identity);
   }
}

for (int i = 0; i < sutunBoyutu * satirBoyutu; i++) kısmında girilen satır ve sütün boyutlarının çarpımı kadar dönecek yani işlem yapacak olan bir for fonksiyonu ekledik.

For döngüsü içinde yer alan “Instantiate” metodunu kullanmak için bizim 3 adet parametreyi karşılayacak bilgilere ihtiyacımız var. Bunlar sahneye eklenecek olan obje, sahnenin neresine ekleneceği bilgisi ve rotasyon bilgisi kısımları.

Ek bilgi: “Instantiate” metodunun kullanımını öğrenmek için “Instantiate Kullanımı” isimli yazımıza ve “Quaternion.identity” için ise Quaternion.identity Kullanımı isimli yazımıza göz atabilirsiniz.

Zaten sahneye eklenecek olan objemiz yani “zeminSablonu” şablonumuz hazır. Rotasyon bilgisi içinse Quaternion.identity yazarak obje ile aynı şekilde kullanacağımızı söylüyoruz. Burada karışık olan ve asıl işlemi yapan objenin pozisyon bilgilerinin girildiği new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + -yBosluk * (i / sutunBoyutu)) Vector3 objesi.

new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + (yBosluk * (i / sutunBoyutu)))

Vector3 sınıfı için gerekli olan x,y ve z koordinatı bilgilerini girmemiz gerekiyor. 3D oyunlarda Y ekseni aslında Z ekseni oluyor. Biraz düşünürseniz Z ekseni derinlemesine sahnede ilerlerken Y ekseni direk yukarı olarak gidendir.Yani objenin pozisyonunu belirlerken X ve Z eksenleri bizim için yeterli olacaktır. Bu sebeple Y ekseni yerine 0f yazılarak, sıfır değeri girilmiştir.

X ve Y için girdiğimiz bilgiler ise aşağıdaki gibi. X değeri için mod alma ve Y değeri için bölme işlemi uyguladık. Aslında burada işimize yarayan sistemi kurduk diyebiliriz.

X ekseni obje pozisyonu: xBaslangic + (xBosluk * (i % sutunBoyutu))
Y ekseni obje pozisyonu: yBaslangic + (yBosluk * (i / sutunBoyutu))

Eğer satır ve sütün sayısına 10 sayısını verirseniz. 10×10 boyutunda bir harita yani ekranda 100 adet zemin objesi istediğiniz anlamına geliyor. Bu durumda for döngümüz 100 kere çalışacaktır. X ve Y değerlerini log olarak bakarsanız da aşağıdaki gibi bir dizilimi olduğunu görürsünüz.

1. Döngü = X:0 Y:0
2. Döngü = X:1 Y:0
.
.
.
10.Döngü = X:9 Y:0
11. Döngü = X:0 Y:1
12. Döngü = X:1 Y:1
.
.
.
20.Döngü = X:9 Y:1
21.Döngü = X:0 Y:2
21.Döngü = X:1 Y:2

Ve bu şekilde devam ediyor. Ama bu kısım kafanızı karıştırmasın, temel mantık belirlediğimiz sütün boyutunda prefab objemizi yan yana oluşturduktan sonra alt pozisyona geçerek aynı işlemi yapması.

Eğer işlemleri belirttiğim gibi yaparsanız aşağıdaki gibi bir sonuç elde etmeniz gerekiyor.

Dinamik Sahne Oluşturma Yapısını Hazırladık

Evet zemin objemizi sahnede istediğimiz boyutlarda dinamik olarak yerleştirebiliyoruz. Fakat “Harita Oluştur” butonuna tekrar basarsanız sahneye tekrardan objeler ekleyecektir yani üzerine yazmaya devam edecektir.

Sahneden Eski Haritayı Silme

Haritamızı oluşturduktan sonra, “Haritayı Temizle” butonuyla silmek istiyorsak bir kaç işlem daha yapmamız gerekiyor. Konumuzun başlarında zaten HaritayiTemizle() isminde bir metot eklemiştik. Şimdi bu metodu çalışır hale getirelim.

Sahneye eklediğimiz objelerimizi silebilmemiz için önce onları yakalayabilmemiz gerekiyor. Sahnede yer alan bir objeyi bulmanın en iyi yolu tag yani etiket kullanmaktır. Bu yüzden “Zemin” isimli prefab dosyamıza “Zemin” isminde bir etiket oluşturarak ekleyelim.

Etiketler konusunda detaylı bilgi almak için Objeye Nasıl Etiket Eklenir isimli yazıma göz atabilirsiniz.

Etiketler konusunda detaylı bilgi almak için Objeye Nasıl Etiket Eklenir isimli yazıma göz atabilirsiniz.

Şablon Objemize Zemin Etiketi Ekledik
Şablon Objemize Zemin Etiketi Ekledik

Etiketimizi ekledikten sonra “HaritaOlusturma” isimli script dosyamızda yer alan “HaritayıTemizle” metodumuzu aşağıdaki gibi değiştiriyoruz.

public void HaritayiTemizle()
{
    var zeminler = GameObject.FindGameObjectsWithTag("Zemin");       

     foreach (var zemin in zeminler)
     {
        DestroyImmediate(zemin);
     }
}

Burada ne kadar kullanılmasını tasvip etmesem de yeni bir yöntem öğrenme için eklediğim “var” anahtar kelimeli farklı bir yapı göreceksiniz.

var Anahtar Kelimesi Nedir?

var” anahtar kelimesini bir değişkene değer atamak için kullanabiliyoruz. Fakat burada “var” kullanabilmemiz için değişkene var sayi = 6; anlık olarak değer atamamız gerekmektedir. var sayi = 6; şeklinde atama yapma ile int sayi = 6; arasında temel olarak bir fark bulunmuyor.

Fakat her ne kadar kısa bir yol olsa da ileride yer alacağınız büyük projelerde kafanızı karıştıracak ve kodu yorumlamanızı zorlaştıracaktır.

Ara bir bilgiden sonra dersimize devam edelim.

  • Kodumuzda eklediğimiz var zeminler = GameObject.FindGameObjectsWithTag("Zemin"); ile sahnede “Zemin” etiketine sahip olan tüm objeleri bularak zeminler listesine eklemiş oluyoruz. Aslında burada yazdığımız var yapısı yerine aşağıdaki gibi yazabilirsiniz.
    GameObject[] zeminler = GameObject.FindGameObjectsWithTag("Zemin");  
  • Eklediğimiz zeminler dizimizin içinde yer alan tüm objelerde gezmek için foreach (var zemin in zeminler) yapısını kullandık.
  • Foreach yapısının her dönüşünde elde ettiğimiz objeyi silmek içinse DestroyImmediate(zemin); kodunu ekledik.

Böylece aşağıdaki gibi bir sonuç elde etmiş olacaksınız.

Sahneyi Temizleme Metodunu Ekledik

Harita Objelerini Taşıma

Butona bastığımızda tüm objelerin sahneye eklendiğini ve sonrasında “Hierarchy” penceresinin karıştığını görebilirsiniz. İlerde sahneye otomatik olarak engeller, bitiş ve başlangıç noktaları gibi objelerde ekleyeceğimiz için şimdiden karışıklığın önüne geçmemizde faydamıza olacaktır.

Bunun için boş bir obje oluşturarak, sahneye eklediğimiz diğer tüm objeleri bu objenin altına ekleyeceğiz yani parent-child ilişkisi kuracağız.

İlk olarak boş bir obje oluşturarak ismini “HaritaElemanlari” olarak değiştirin. Sonrasında ise objemize yine aynı isimde yani “HaritaElemanlari” isminde bir etiket ekliyoruz.

harita elemanlari objesi
Harita Elemanları Üst Objesini Oluşturduk

Şimdi aşağıdaki kodu ekleyerek iki adet Gameobject türünde değişken oluşturuyoruz.

private GameObject haritaElemanlariUstObjesi, sahneyeEklenenObje;

Buraya eklediğimiz “haritaElemanlariUstObjesi” değişkenine “HaritaElemanlari” etiketine sahip objeyi arattırarak aktaracağız. Bunun için HaritaOluştur() metodunun en üst satırına aşağıdaki kodu ekliyoruz.

haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

Bir sonraki aşamamız ise daha önceden oluşturduğumuz for fonksiyonunda değişiklik yapmak olacak. Bunun için “Instantiate” metodu ile eklediğimiz yapıyı “sahneyeEklenenObje” değişkenine atıyoruz. sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform); yazarak ise sahneye eklediğimiz objelerin “HaritaElemanlari” objesinin altında yer almasını söylüyoruz.

for (int i = 0; i < sutunBoyutu * satirBoyutu; i++)
{
   sahneyeEklenenObje = Instantiate(zeminSablonu, new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + -yBosluk * (i / sutunBoyutu)), Quaternion.identity);
   sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);

}

Biraz kodlar kafanızı karıştırmış ya da sıralarını karıştırmış olabilirsiniz ama merak etmeyin kodun tamamını paylaşacağım. Ama son olarak 22. satırda for döngüsünü neden if bloğuna soktuğumuza bakalım. if koşulunda yazan haritaElemanlariUstObjesi.transform.childCount == 0 koduyla sahneye eklediğimiz objenin içinde alt objeler var mı, yok mu diye kontrol ediyoruz. Böylece eğer yoksa oyun için haritayı oluşturuyor ama eğer alt obje zaten varsa bu sefer önce HaritayiTemizle(); ve sonrasında HaritayiOlustur(); metotlarını çağırıyor. Böylece sahneyi önce temizleyip sonra da tekrar oluşturuyor.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HaritaOlusturma : MonoBehaviour
{
    [SerializeField] private GameObject zeminSablonu;
    [SerializeField] private float xBaslangic;
    [SerializeField] private float yBaslangic;
    [SerializeField] private int sutunBoyutu;
    [SerializeField] private int satirBoyutu;
    [Range(1, 2)] [SerializeField] private int xBosluk;
    [Range(1, 2)] [SerializeField] private int yBosluk;

    private GameObject haritaElemanlariUstObjesi, sahneyeEklenenObje;


    public void HaritayiOlustur()
    {
        haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

        if (haritaElemanlariUstObjesi.transform.childCount == 0)
        {
            for (int i = 0; i < sutunBoyutu * satirBoyutu; i++)
            {
                sahneyeEklenenObje = Instantiate(zeminSablonu, new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + (yBosluk * (i / sutunBoyutu))), Quaternion.identity);
                sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
            }
        }
        else
        {
            HaritayiTemizle();
            HaritayiOlustur();
        }
    }

    public void HaritayiTemizle()
    {
        var zeminler = GameObject.FindGameObjectsWithTag("Zemin");
        foreach (var zemin in zeminler)
        {
            DestroyImmediate(zemin);
        }
    }

}

Çalışmamız sonrasında aşağıdaki gibi bir sonuç elde etmiş olmanız gerekiyor.

Harita Oluşturmanın İlk Aşamasının Tamamlanmış Hali

Bu yazıdaki dersimiz daha bitmedi ama bir yandan yayınlamam konusunda ısrar eden arkadaşlar için bu kısma kadar paylaştım. Bu içerik üzerinden aşağıdaki konu başlıklarına da değineceğiz.

  • Haritada rastgele engel oluşturulması için bir fonksiyon yazacağız.
  • Haritada rastgele başlangıç noktaları oluşturması için bir fonksiyon yazacağız.
  • Haritada rastgele bitiş noktaları oluşturması için bir fonksiyon yazacağız.

Haritada Rastgele Şekilde Engel Oluşturma

Bu bölümde haritada oluşacak engelleri ayarlayacağız. Tower Defence oyunu başlamadan önce belirleyeceğimiz sayı kadar, haritada rastgele şekilde engeller oluşmasını sağlayacağız. Bu engellerden hem düşman dalgaları geçemeyecek hem de biz de üzerine kule kurulumu yapamayacağız. Amacımız oyuna biraz farklılıklar eklemek, böylece yeni şeyler öğrenebiliriz.

O zaman başlayalım…

İlk yapmamız gereken EngelOluştur() isminde bir metot eklemek ve bu metodu butona bastığımızda da çağrılması olacak. Bu yüzden en mantıklı yer daha önceden oluşturduğumuz for döngüsünün bitişinden sonra çağırmamız daha mantıklı olacaktır. Yani zemin için kullandığımız yapı tamamlandıktan sonra üstüne engelleri koymak olacak.

public void HaritayiOlustur()
    {
        haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

        if (haritaElemanlariUstObjesi.transform.childCount == 0)
        {
            for (int i = 0; i < sutunBoyutu * satirBoyutu; i++)
            {
                sahneyeEklenenObje = Instantiate(zeminSablonu, new Vector3(xBaslangic + (xBosluk * (i % sutunBoyutu)), 0f, yBaslangic + (yBosluk * (i / sutunBoyutu))), Quaternion.identity);
                sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
            }
            
            EngelOlustur();
        }
        else
        {
            HaritayiTemizle();
            HaritayiOlustur();
        }
    }

13. satıra “EngelOlustur” isminde bir metot ekledik. Sonrasında ise aşağıdaki gibi bir boş metot ekledik. Şimdi bu metodun içerisini dolduracağız.

public void HaritayiTemizle()
    {
        var zeminler = GameObject.FindGameObjectsWithTag("Zemin");
        foreach (var zemin in zeminler)
        {
            DestroyImmediate(zemin);
        }
    }

    private void EngelOlustur()
    {

    }

Peki bu sistemi hazırlamak için bize lazım olacaklar ne bir düşünelim.

  • Engel için kullanabileceğimiz bir obje.
  • “Inspector” penceresinden kaç adet engelin girilebileceğini belirteceğimiz bir alan.
  • Haritada hangi yerlere engelleri koyacağımızı belirleyebilmek için “X” ve “Z” pozisyon bilgileri.
  • Engellerin üst üste gelmesini engelleyebilmek için bir yapı kurmak.

Bize lazım olacak değişkenleri aşağıdaki gibi ekleyelim. Daha önceden eklediğimiz değişkenlerin altında yani tüm metotların üstüne eklemeniz gerekiyor.

[SerializeField] private GameObject engelObjesiSablonu;
[Range(4, 24)][SerializeField] private int engelSayisi;
private List<Vector3> engeller = new List<Vector3>();
private int engelX, engelZ;

Burada private List engeller = new List(); eklememizin sebebi; oluşturduğumuz bir engelin üzerine farklı bir engel gelmemesi için. Bu listenin içine her oluşturduğumuz engel objesinin pozisyon bilgisini ekleyeceğiz ve yeni bir ekleme yapmadan önce bu listeden kontrol ederek aynı pozisyonda farklı bir engel varsa yeniden pozisyon bilgisi bulmaya çalışacağız.

Ama öncesinde engel için kullanacağımız objemizi belirleyelim. Bunun için daha önceden projemize import ettiğimiz asset ler içinde yer alan “Trap 01” objesini kullanabiliriz. “Assets/Meshtint Free Tile Map Mega Toon Series/Prefabs” içinde bulunan “Trap 01” objesini kopyalayarak “Assets/Prefabs” içerisine atın ve ismini “Engel” olarak değiştirin.

Sahneyi temizlerken bu objeyi bulabilmemiz içinse “Engel” isminde yeni bir etiket oluşturarak direk prefab üzerinden eklemesini gerçekleştireceğiz.

engel etiketini ekleme
Engel objelerine etiket ekledik

Tüm engel oluşturma yapısı için oluşturduğum sistemin kodlarını ekliyorum. Sonrasında tek tek ne yaptığımızı inceleyeceğiz.

private void EngelOlustur()
{
    haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

    while (engeller.Count < engelSayisi)
    {
        engelX = Random.Range(0, satirBoyutu);
        engelZ = Random.Range(0, sutunBoyutu);

        if (!engeller.Contains(new Vector3(engelX, 0f, engelZ)))
        {
            engeller.Add(new Vector3(engelX, 0f, engelZ));
        }
    }

    for (int ii = 0; ii < engeller.Count; ii++)
    {
        sahneyeEklenenObje = Instantiate(engelObjesiSablonu, engeller[ii], Quaternion.identity);
        sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
    }
    engeller.Clear();
}
  • haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari"); kısmında sahnede yer alan ve tüm objeleri içine child olarak eklediğimiz “HaritaElemanlari” objesini etiketinde yakalıyoruz.
  • while (engeller.Count < engelSayisi) yazarak eklediğimiz “engeller” listesinin eleman sayısını(count), harita oluştururken girdiğimiz engel sayısı üzerinden kontrol ediyoruz. Buradaki mantık aslında şu şekilde. Biz sahneye her eklediğimiz objeyi bu listeye ekliyoruz. Sonrasında ise “While” döngüsü sayesinde listedeki eleman sayısını girdiğimiz engel sayısına denk gelene kadar while döngüsü içindeki kodları çalıştırıyoruz.
  • engelX = Random.Range(0, satirBoyutu); ve engelZ = Random.Range(0, sutunBoyutu); yazdığımız kısımlarda ise rastgele sayı üretme yapısını kullanıyoruz. “Random.Range” sayesinde içerisine girdiğimiz iki sayı arasında rastgele bir sayı üretecektir. Bu sayıları üretme sebebimiz ise, engellerin sahnede oluşacağı x ve z koordinatlarını belirleyebilmek.
  • Daha önceden bahsettiğim engellerin aynı pozisyona denk gelerek üst üste binmesini engellemeyi kontrol etmemiz gerekiyor. if bloğu içinde kullandığımız !engeller.Contains(new Vector3(engelX, 0f, engelZ)) bu yapı ile engeller listemizin içerisinde “Random.Range” ile rastgele üretilen koordinatlarda başka engel objesi olup olmadığını kontrol ediyoruz.
  • Yukarıda kullandığımız “Contains” ile listemizin içinde aynı koordinant bilgisi var mı bakabiliyoruz ve eğer yoksa “Add” metodu ile engeller.Add(new Vector3(engelX, 0f, engelZ)); yazarak listemize ekleme yapıyoruz.
  • Sonrasında ise bir for döngüsü ile listemizin içinde yer alan eleman sayısı kadar sahneye obje ekliyoruz. sahneyeEklenenObje = Instantiate(engelObjesiSablonu, engeller[ii], Quaternion.identity); ile objeyi sahneye ekledikten sonra sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform); ile de “HaritaElemanlari” objesinin alt objesi haline getirdik.
  • Son olarak ise “Clear” metodu ile “engeller” listemizin içini temizlememiz gerekiyor. Bunun için engeller.Clear(); yazmamız yeterli olacaktır. “Clear” yapmamızın sebebi ise bir kere liste oluşturduktan sonra yeniden bir harita oluşturmak istersek bize yine aynı pozisyon bilgilerini verecektir. Bu şekilde temizleyerek her seferinde listeye yeni elemanlar eklemesini sağlıyoruz.

Bu şekilde engelleri oluşturma listemiz hazır sayılır. Sadece yapmamız gereken bir işlem kaldı. Bu işlemde “HaritayiTemizle” fonksiyonuna aşağıdaki kodu eklemek. Böylece haritayı temizle butonuna basarak sahnede yer alan engelleri de silebileceğiz.

public void HaritayiTemizle()
{
    var zeminler = GameObject.FindGameObjectsWithTag("Zemin");
    var engeller = GameObject.FindGameObjectsWithTag("Engel");

    foreach (var zemin in zeminler)
    {
        DestroyImmediate(zemin);
    }

    foreach (var engel in engeller)
    {
        DestroyImmediate(engel);
    }

}

Son durumda aşağıdaki gibi bir yapı elde etmiş olacağız. Eğer takıldığınız bir kısım varsa yorum alanından çekinmeden yazabilirsiniz.

Unity Tower Defans Oyununda Dinamik Engel Oluşturma Kısmının Tamamlanmış Hali

Başlangıç Noktalarını Oluşturuyoruz

Bu konu başlığının altında ise haritamızda rastgele pozisyonda iki adet noktada düşmanların çıkacağı başlangıç noktaları ekleyeceğiz. Haritada engel oluşturma yapısına benzer bir yapı olacağı için daha kolay anlayabileceğinizi düşünüyorum.

Burada engel oluşturma yapısından farklı olarak başlangıç noktalarını sadece haritanın en üst kısmındaki alanda oluşturulmasını sağlayacağız. Böylece hem saçma yerlerde ortaya çıkarak oyunun yapısını bozmasını engellemiş hem de yeni bir şey daha öğrenmiş olacağız.

baslangic noktalarinin olusacagi alan
Başlangıç Noktalarının Oluşacağı Alan

Tabi ilk başta düşmanların ortaya çıkacağı başlangıç noktaları için kullanacağımız objeyi belirleyelim. Bunun için yine projeye aktardığımız paketin içinde yer alan “Waypoint 01” şablonunu kullanabiliriz. “Assets/Meshtint Free Tile Map Mega Toon Series/Prefabs/Waypoint 01” objesini kendi “Assets/Prefabs” klasörünün içerisine kopyalayın.

Objemizin ismini “BaslangicNoktasi” olarak değiştirdikten sonra yine aynı isimde bir etiket oluşturarak objeye ekleyin. Engel objelerinde olduğu gibi bu objemize de “BaslangicNoktasi” ismindeki etiket vermemizin sebebi sahneden objeleri silerken etiket ismine göre işlem yapmamız.

Şimdi ise bize lazım olacak aşağıdaki değişkenleri “HaritaOlusturma” script dosyamızın en üstüne ekleyelim.

[SerializeField] private List<Vector3> baslangicNoktalari = new List<Vector3>();
[SerializeField] private GameObject baslangicNoktasiSablonu;
private int baslangicNoktasiX;

private List baslangicNoktalari = new List(); – Rastgele oluşan başlangıç noktalarının pozisyon bilgileri atmak için bu listeyi kullanacağız.

private GameObject baslangicNoktasiSablonu; – Başlangıç noktası için kullandığımız şablonu dışarıdan eklemek için kullanacağımız değişken.

private int baslangicNoktasiX; – Başlangıç noktalarının X koordinat bilgilerini tutacağımız değişken. Buraya Z ekseni için değişken bilgisi tutmuyoruz çünkü sadece kullanıcının eklediği satır sayısının en üst kısmını kullanacağız.

Değişkenlerimizi hazırladık ve sıra fonksiyonumuzu oluşturmaya geldi. Bunun için aşağıda hazırladığım yapıyı kullanacağız. Bu fonksiyonu direk “EngelOlustur” metodundan sonra ekleyebilirsiniz.

private void BaslangicNoktasiOlustur()
{
    haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

    while (baslangicNoktalari.Count < 2)
    {
        baslangicNoktasiX = Random.Range(0, sutunBoyutu);

        if (!baslangicNoktalari.Contains(new Vector3(baslangicNoktasiX, 0f, satirBoyutu - 1)))
        {
            if (!engeller.Contains(new Vector3(baslangicNoktasiX, 0f, satirBoyutu - 1)))
            {
                baslangicNoktalari.Add(new Vector3(baslangicNoktasiX, 0f, satirBoyutu - 1));
            }
        }
    }

    for (int i = 0; i < baslangicNoktalari.Count; i++)
    {
        sahneyeEklenenObje = Instantiate(baslangicNoktasiSablonu, baslangicNoktalari[i], Quaternion.identity);
        sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
    }

    baslangicNoktalari.Clear();
}
  • haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari"); yazarak sahnede “HaritaElemanlari” ismine sahip objeyi buluyoruz. Bulduğumuz bu objenin içerisine oluşturduğumuz objeyi sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform); yazarak alt obje olarak ekleyeceğiz.
  • while (baslangicNoktalari.Count < 2) kısmında engel oluşturma yapısında gördüğümüz gibi listenin sayısına bakarak “While” döngüsüne sokuyoruz. Bu sefer en fazla 2 adet başlangıç noktası istediğimiz için el ile giriş yaptık.
  • baslangicNoktasiX = Random.Range(0, sutunBoyutu); yazarak “Random.Range” metodu ile rastgele bir sayı üreterek sahnede oluşacak başlangıç objelerinin X koordinat bilgisini bulduk.
  • if (!baslangicNoktalari.Contains(new Vector3(baslangicNoktasiX, 0f, satirBoyutu - 1))) yazarak ise başlangıç noktalarının aynı yerde çıkmasını engellemek için “Contains” ile kontrol ediyoruz.
  • Yine aynı şekilde if (!engeller.Contains(new Vector3(baslangicNoktasiX, 0f, satirBoyutu - 1))) yazarak bu seferde sahnede yer alan engeller ile başlangıç noktalarının üst üste gelmesini engelliyoruz.
  • satirBoyutu – 1 yazarak girdiğimiz satır boyutu bilgisinin en üstünü satırı bilgisini alıyoruz. Burada -1 değerini girmemizin sebebi sahnede objeleri sıfır koordinatından başlatmamız. Şöyle düşünün eğer siz satır boyutu 15 olarak girerseniz aslında 0,1,2….13,14 olarak sıralanacağı yani sınırdan başlayacağız için aslında Z koordinatının en üst sayısı 14 olacaktır.
  • Objelerimizi sahnede rastgele olarak belirlenmiş pozisyonlarda “Instantiate” komutu ile oluşturmak için sahneyeEklenenObje = Instantiate(baslangicNoktasiSablonu, baslangicNoktalari[i], Quaternion.identity); yazıyoruz ve for döngüsüne sokarak listenin eleman sayısı kadar döndürüyoruz.
  • for döngüsünün sonunda ise baslangicNoktalari.Clear(); yazarak objelerin pozisyon bilgisini listeden temizliyoruz. Böylece her “Harita Oluştur” butonuna bastığımızda objelerimiz farklı yerlerde oluşacaktır.

Fonksiyonumuz tamam peki bu fonksiyonu script içerisinden nereden çağırmamız gerekiyor bunu bulmamız lazım. En mantıklı kısım EngelOlustur() fonksiyonun içinde sahnede engeller oluştuktan sonra olacaktır. Bunun için aşağıda işaretli kısımdaki gibi BaslangicNoktasiOlustur(); fonksiyonunu çağırıyoruz.

private void EngelOlustur()
{
    haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

    while (engeller.Count < engelSayisi)
    {
        engelX = Random.Range(0, sutunBoyutu);
        engelZ = Random.Range(0, satirBoyutu);

        if (!engeller.Contains(new Vector3(engelX, 0f, engelZ)))
        {
            engeller.Add(new Vector3(engelX, 0f, engelZ));
        }
    }

    for (int i = 0; i < engeller.Count; i++)
    {
        sahneyeEklenenObje = Instantiate(engelObjesiSablonu, engeller[i], Quaternion.identity);
        sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
    }

    BaslangicNoktasiOlustur();
    engeller.Clear();
}

Tek eksiğimiz her yeni “Harita Oluştur” ya da “Haritayı Temizle” butonuna bastığımızda eskiden oluşan başlangıç noktalarını silmek olacaktır. Bunun içinse “HaritayiTemizle” fonksiyonu içerisine aşağıda işaretli kodları ekliyoruz.

public void HaritayiTemizle()
{
    var zeminler = GameObject.FindGameObjectsWithTag("Zemin");
    var engeller = GameObject.FindGameObjectsWithTag("Engel");
    var baslangicNoktalari = GameObject.FindGameObjectsWithTag("BaslangicNoktalari");

    foreach (var zemin in zeminler)
    {
        DestroyImmediate(zemin);
    }

    foreach (var engel in engeller)
    {
        DestroyImmediate(engel);
    }

    foreach (var baslangic in baslangicNoktalari)
    {
        DestroyImmediate(baslangic);
    }

}

Eğer bir kısmı atlamadıysanız bölüm sonunda aşağıdaki gibi bir sonuç elde etmeniz gerekiyor.

Başlangıç Noktalarının Rastgele Eklenmesinin Tamamlanmış Hali

Haritaya Bitiş Noktalarının Eklenmesi

Başlangıç noktaları ve engelleri ekleyecek dinamik yapımızı hazırladığımıza göre şimdi sırada bitiş noktalarını eklemek var. Oyunumuzdaki ana senaryoda düşmanlar iki farklı noktadan çıkarak iki farklı bitiş noktasına gitmeye çalışacaklar. Ayrıca bitiş noktalarının haritanın alt kısımlarında yani başlangıç noktasından uzakta bir pozisyonda olması gerekiyor ki oynanabilirliğini kaybetmesin. Bu yüzden haritanın belirli bir bölüme iki tane bitiş noktası eklemek için sistemimizi hazırlayalım.

Önce bize lazım olacak değişkenleri ekleyelim.

[SerializeReference] private GameObject[] bitisNoktasiSablonlari = new GameObject[2];
[Range(1, 4)] [SerializeReference] private int bitisNoktasiPosizyon;
private List<Vector3> bitisNoktalari = new List<Vector3>();
private int bitisNoktasiX, bitisNoktasiZ;
  • private GameObject[] bitisNoktasiSablonlari = new GameObject[2]; yazarak 2 elemanlı GameObject türünde olan array yapımızı ekledik. Bu array içerisinde bitiş noktaları için kullanacağımız objeler yer alacak.
  • Haritanın hangi kısmında olacağını belirlemek için private int bitisNoktasiPosizyon; yazarak “Integer” bir değişken oluşturduk. Bu değişkeni fonksiyon içerisinde kullanarak bitiş noktaları için bir alan belirleyeceğiz. Bu kısım yeni olduğu için biraz detaya gireyim. Değişkenin başında [Range(1, 4)] yazdığını görmüşsünüzdür.

    Burada oyun başlamadan önce Inspector penceresi üzerinden 1 ile 4 arasında bir sayı gireceğiz. Girdiğimiz sayının anlamı ise haritanın alt kısmından başlayarak kaç kare kadar bir alanda bitiş noktaları oluşmasını istediğimizi belirtiyoruz. Örneğin 4 sayısını seçersek aşağıdaki görselde olduğu gibi en alt kısımdan, 4 kare kadar yukarı olan bir kısımda bitiş noktaları oluşacaktır.
haritada bitis noktalarinin pozisyonu
Haritada bitiş noktaları hangi alanda oluşacak
  • bitisNoktalari = new List(); yazarak bir liste oluşturduk. Bu sayede oluşan bitiş noktalarının pozisyonlarını bu listeye atacağız. Böylece yeni bitiş noktası eklerken buradaki pozisyonları kontrol ederek üst üste gelmesini engelleyeceğiz.
  • private int bitisNoktasiX, bitisNoktasiZ; ileyse tabi ki bitiş noktalarımızın pozisyon bilgilerini tutmak için “Integer” değişkenlerimiz.

Şimdi sıra bitiş noktaları için bir fonksiyon oluşturmaya geldi.

private void BitisNoktasiOlustur()
{
    haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari");

    while (bitisNoktalari.Count < bitisNoktasiSablonlari.Length)
    {
        Debug.Log("While girdi");
        bitisNoktasiX = Random.Range(0, sutunBoyutu);
        bitisNoktasiZ = Random.Range(0, bitisNoktasiPosizyon);

        if (!bitisNoktalari.Contains(new Vector3(bitisNoktasiX, 0f, bitisNoktasiZ)))
        {
            if (!engeller.Contains(new Vector3(bitisNoktasiX, 0f, bitisNoktasiZ)))
            {
                bitisNoktalari.Add(new Vector3(bitisNoktasiX, 0f, bitisNoktasiZ));
            }

        }

    }

    for (int i = 0; i < bitisNoktalari.Count; i++)
    {
        sahneyeEklenenObje = Instantiate(bitisNoktasiSablonlari[i], bitisNoktalari[i], Quaternion.identity);
        sahneyeEklenenObje.transform.SetParent(haritaElemanlariUstObjesi.transform);
    }
    bitisNoktalari.Clear();
}
  • haritaElemanlariUstObjesi = GameObject.FindWithTag("HaritaElemanlari"); yazarak sahnede yer alan “HaritaElemanlari” objesini buluyoruz.
  • Bitiş noktalarının sayısı kadar bitiş noktaları olup, olmadığını anlamak için “While” döngüsünü kullanarak while (bitisNoktalari.Count < bitisNoktasiSablonlari.Length) yazdık. Biraz sonra içerisine ekleme yapacağımız “bitisnoktalari” array de bulunan eleman sayısı, şablon sayısından küçük olduğu sürece ekliyoruz.
  • bitisNoktasiX = Random.Range(0, satirBoyutu); yazarak belirlediğimiz sayılar arasında X ekseninde rastgele bir pozisyon oluşturmasını sağlıyoruz. Yani burada 0 ile kullanıcının girdiği satır sayısı arasında bir pozisyonda X ekseninin olmasını istedik.
  • Z eksenini belirlemek içinse bitisNoktasiZ = Random.Range(0, bitisNoktasiPosizyon); yazdık. Burada Z ekseni 0’dan başlayabilir yani en alttan başlayarak bizim belirlediğimiz “bitisNoktasiPosizyon” değerine kadar olan bir alanda oluşabilir diyoruz.
  • Haritada oluşturacağımız başlangıç noktaları haritanın yukarı kısımda yer alacağı için üst üste denk gelemezler. Fakat engellerle aynı pozisyonda olma ihtimalleri bulunuyor. Bu yüzden if (!engeller.Contains(new Vector3(bitisNoktasiX, 0f, bitisNoktasiZ))) yazarak engeller listesinin pozisyon bilgileri “Contains” ile kontrol ederek aynı pozsiyonda denk gelmediyse ekliyoruz.
  • Son olarak ise bitisNoktalari.Clear(); yazarak “Inspector” penceresinde her “Harita Oluştur” pozisyonuna bastığımızda array içindeki elemanların silinmesini sağlıyoruz. Böylece sürekli aynı pozisyonda oluşmuyorlar.

Şimdi sırada bitiş noktaları için kullanacağımız objeleri ve etiketlerini ayarlamaya geldi. İndirdiğimiz pakette yer alan iki adet obje işimize yarayacak gibi gözüküyor. Zaten çalışmamızın ileriki aşamalarında gönüllü arkadaşlar bize çalışmalarını gönderirlerse onları kullanabiliriz.

İndirdiğimiz paketin “Meshtint Free Tile Map Mega Toon Series/Prefabs” klasöründe yer alan “Button Platform 01 Blue” ve “Button Platform 01 Red” objelerini “Assets/Prefabs” klasörünüze kopyalayın. Objelerin isimlerini ise renklerine göre “MaviBitisNoktasi” ve “KirmiziBitisNoktasi” olarak değiştirin.

“Haritayı Temizle” butonuna bastığımızda objelerin sahneden temizlenmesi için her iki bitiş noktasına da “BitisNoktalari” isminde etiket oluşturarak ekleyeceğiz.

Tabi “HaritayiTemizle()” fonksiyonumuzun içerisine aşağıda işaretli olan kısımları da eklememiz lazım ki etiket üzerinden bularak haritadan silebilsin.

public void HaritayiTemizle()
{
    var zeminler = GameObject.FindGameObjectsWithTag("Zemin");
    var engeller = GameObject.FindGameObjectsWithTag("Engel");
    var baslangicNoktalari = GameObject.FindGameObjectsWithTag("BaslangicNoktalari");
    var bitisNoktalari = GameObject.FindGameObjectsWithTag("BitisNoktalari");

    foreach (var zemin in zeminler)
    {
        DestroyImmediate(zemin);
    }

    foreach (var engel in engeller)
    {
        DestroyImmediate(engel);
    }

    foreach (var baslangic in baslangicNoktalari)
    {
        DestroyImmediate(baslangic);
    }

    foreach (var bitis in bitisNoktalari)
    {
        DestroyImmediate(bitis);
    }

}

Evet Unity ile 3D Tower Defence oyunu yapma projemizde bitiş noktalarını da rastgele bir şekilde ekleyen sistemi yapmış olduk. Eğer sizde adım adım dediklerimi yaptıysanız aşağıdaki gibi bir sonuç elde etmiş olmalısınız.

Unity Tower Defece Dinamik Bitiş Noktası