Evet arkadaşlar birlikte yaptığımız ilk oyun geliştirme projemize bu bölümde Tower Defence oyunumuza yapay zekayı ekleyeceğiz. 1. Bölüm dersinde sahne tasarımımızı yapmıştık hatta sahnemizi oluşturacak dinamik bir yapı oluşturmuştuk.
Hatırladığınız üzere oyunumuzda iki başlangıç ve iki adette bitiş noktamız vardı. Şimdi bu başlangıç noktalarından çıkan düşmanları bitiş noktasına doğru hareket ettireceğiz. Bunun için Unity AI yapısını kullanacağız. Şimdi bu dersi hazırlarken Unity AI dersimizde yaptığımızı yapıyı birebir uygulayıp geçebilirdim. Yani bir navigasyon oluştururduk ve düşmanlar bu navigasyon üzerinden oluşturduğumuz haritayı takip ederek bitiş noktalarına giderlerdir.
Fakat bu sefer yeni bir şey öğrenemezdik. Bu sebeple farklı bir yapı oluşturmaya karar verdim. Yeni şeyler öğreneceğiz ama ilerde işlerimizi oldukça zorlayacak lakin değeceğini düşünüyorum.
Aklımdaki yapı şu şekilde;
Başlangıç noktalarımızın her birinden farklı düşman tipleri çıkacak ve her zaman belirli bir bitiş noktasına gidecekler. Şöyle düşünün birinci başlangıç noktasından çıkan düşman dalgaları birinci bitiş noktasına ve ikinci başlangıç noktasından çıkan düşman dalgaları ise ikinci bitiş noktasına doğru gidecekler.
Bu işlemi yapabilmek için Unity Navigation yapısı yetersiz kalıyor çünkü tek bir harita çıkarabiliyorsunuz. Unity Yapay Zeka dersinde de bu durumdan bahsetmiştim ve ileride birden fazla harita oluşturmak için NavMesh Surface yapısını öğreneceğimizi söylemiştim. Hem sözümü tutayım hem de yeni bir tecrübe etmiş olalım.
Not: Eğer daha önceki unity yapay zeka dersimizi incelemediyseniz mutlaka göz atmanızı öneriyorum.
Düşman Modellemelerini Projeye Ekliyoruz
Yapay zeka kısmına geçmeden önce başlangıç noktalarından hareket edecek iki farklı düşman objesi ekleyeceğim. Tabi bunları navigasyon yapısını test etmek için eklemiş olacağız. Yani sonraki derslerimizde yapılarını, animasyonlarını ayarlamalarını yapacağız.
Düşman karakterleri için yine Asset Store’yi ziyaret ediyoruz. Şansımıza zemin objelerini indirdiğimiz firmanın eklediği ve tam istediğimiz gibi olan iki farklı modelleme buldum. Aşağıda belirttiğim adreslerde yer alan iki modellemeyi de indirerek projemize import ediyoruz. Her biri 2.1 MB ve ücretsiz 🙂

Projemize import ettikten sonra “Meshtint Free Boximon Cyclopes Mega Toon Series/Boximon Cyclopes” objesini kopyalayarak “Assets/Prefabs” klasörüne kopyalıyoruz ve ismini “BirinciDusman” olarak değiştiriyoruz. Aynı şekilde “Meshtint Free Boximon Fiery Mega Toon Series/Prefabs/Boximon Fiery” objesini kopyalayarak “Assets/Prefabs” klasörümüze atıyoruz ve ismini “IkinciDusman” olarak değiştiriyoruz.
Düşman Modellemelerini Haritaya Ekliyoruz
Modellemeleri projemize eklediğimize göre artık oyunumuz başladığında düşman modellemelerini harita yer alan başlangıç noktalarında oluşmasını sağlamamız gerekiyor. Bunun için yeni bir C# Script dosyası oluşturalım ve ismini “DusmanDalgalariYoneticisi” olarak belirleyelim. Tüm işlemleri bu script dosyası içerisinden yapacağız.
Aşağıdaki kodları script dosyamıza ekleyelim.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DusmanDalgalariYoneticisi : MonoBehaviour { public GameObject[] baslangicNoktalari; [SerializeField] private GameObject maviDusman, kirmiziDusman; [SerializeField] private GameObject maviBitisNoktasi, kirmiziBitisNoktasi; void Start() { baslangicNoktalari = GameObject.FindGameObjectsWithTag("BaslangicNoktalari"); maviBitisNoktasi = GameObject.Find("MaviBitisNoktasi(Clone)"); kirmiziBitisNoktasi = GameObject.Find("KirmiziBitisNoktasi(Clone)"); HaritayiOlustur(); } private void HaritayiOlustur() { for (int i = 0; i < baslangicNoktalari.Length; i++) { if (i == 0) { Vector3 maviBitisFark = maviBitisNoktasi.transform.position - baslangicNoktalari[i].transform.position; Instantiate(maviDusman, baslangicNoktalari[i].transform.position, Quaternion.LookRotation(maviBitisFark, Vector3.up)); } else if (i == 1) { Vector3 kirmiziBitisFark = kirmiziBitisNoktasi.transform.position - baslangicNoktalari[i].transform.position; Instantiate(kirmiziDusman, baslangicNoktalari[i].transform.position, Quaternion.LookRotation(kirmiziBitisFark, Vector3.up)); } } } }
public GameObject[] baslangicNoktalari
yazarak bir array oluşturduk veStart()
fonksiyonunun içindebaslangicNoktalari = GameObject.FindGameObjectsWithTag("BaslangicNoktalari"); yazarak "BaslangicNoktalari
” etiketine sahip tüm objeleri oluşturduğumuz array içerisine atmış olduk. Böylece elimizde oyunda yer alan 2 başlangıç noktasının olduğu bir array olmuş oldu.[SerializeField] private GameObject maviDusman, kirmiziDusman;
yazarak dışarıdan düşman objelerini ekleyebileceğimiz “GameObject” türünde 2 adet değişkenimiz oldu. Düşman prefabs objelerini bu değişkenlere ekleyeceğiz.[SerializeField] private GameObject maviBitisNoktasi, kirmiziBitisNoktasi;
ile de bitiş noktalarını atayacağımız iki adet değişken eklemiş olduk.- İleride kodlarımızın çok karışmaması için “HaritayiOlustur” isminde ek bir fonksiyon ekledik. Bu fonksiyonu Start() fonksiyonun içinde çağırmak için
HaritayiOlustur();
yazdık. - “HaritaOlustur” fonksiyonunun içerisine
for (int i = 0; i < baslangicNoktalari.Length; i++)
yazarak bir for döngüsü ekledik. Bu döngü sayesinde başlangıç noktalarının yer aldığı array elemanlarını gezeceğiz. Array’in eleman sayısının 2 olduğunu biliyoruz çünkü iki adet başlangıç noktamız var. Bu başlangıç noktalarından birincisi yani mavi renkli düşmanımıza erişmek içinif (i == 0)
bloğunu ve kırmızı olana erişmek içindeelse if (i == 1)
bloğunu kullanacağız. - Haritada yer alan başlangıç noktalarının haricinde bitiş noktalarını da bulmamız gerekiyor. Bunun için
maviBitisNoktasi = GameObject.Find("MaviBitisNoktasi(Clone)");
vekirmiziBitisNoktasi = GameObject.Find("KirmiziBitisNoktasi(Clone)");
yazarak bitiş noktalarını isimlerinden buluyoruz.
Burada isimlerin sonunda “(Clone)” yazdığını görmüşsünüzdür. Eğer sahnede oluşturduğumuz prefabs şablonlarının ismine bakarsanız hepsinin sonunda “Clone” yazdığını görürsünüz. Dinamik bir harita yapımız olduğu için yani bitiş noktaları farklı pozisyonlarda olabileceği için biz de bu şekilde isimlerinden arattırarak bitiş noktalarını tespit ediyoruz. - “maviBitisFark” ve “kirmiziBitisFark” isminde ve Vector3 türünde iki adet obje oluşturduğumuzu görmüşsünüzdür. Burada yapmak istediğimiz başlangıç ve bitiş noktaları arasındaki mesafeyi bulabilmek. Aralarındaki mesafeyi ise sahnede objeyi oluştururken
Quaternion.LookRotation(maviBitisFark, Vector3.up)
yazarak rotasyonunu oluşturmak için alıyoruz.
Burada “Instantiate” objemizi oluştururken 3 adet parametre giriyorduk. Hangi objenin oluşacağı, hangi pozisyonda olacağı ve hangi rotasyonda yani açıda duracağı. Bizim başlangıç ve bitiş arasındaki farkı almamızın asıl amacı objemiz sahneye eklendiği anda direk gideceği noktaya yani bitiş noktasına göre rotasyonunu ayarlamasını sağlamak. Yani düşmanlar gidecekleri yere doğru dönecekler.
Unity Quaternion sınıfı dersinde “LookRotation” ile ilgili bir bölümümüz var ve bu dersteki örnek videoya bakmanızı tavsiye ederim.
Evet son olarak sahnemize bir “Create Empty” ekleyelim ve ismini “YolHesaplama” yapalım. Bu objemize de “DusmanDalgalariYoneticisi” scriptimizi atarak oyunumuzu başlattığımızda düşman modellemelerinin sahneye eklenmesi ve gidecekleri yöne doğru dönmüş olmaları gerekiyor.
Düşmanların Yapay Zekasını Hazırlıyoruz
Dersin başında bahsettiğim gibi yapay zeka için Unity Navigation yapısı bizim için yetersiz kalıyor. Bu sebeple Unity ile birlikte gelmeyen ama ek olarak projenize import edebileceğiniz NavMeshSurface bileşenini kullanacağız.
NavMeshSurface bize hem birden fazla ajan tipi için harita yapısı çıkaracak hem de oyun öncesinde değil de oyun sırasında haritayı oluşturmamızı sağlayacak.
NavMeshSurface Bileşenini Projeye Ekleme
Unity resmi sitesinde kaynak adres olarak gösterilen ve GitHub üzerinden olan aşağıdaki bağlantıdan NavMesh bileşenini indiriyoruz.
GitHub üzerinden indirme işlemi hiç yapmamış arkadaşlar aşağıda görselde gözüktüğü gibi “Code” kısmına tıkladıktan sonra “Download ZIP” kısmına tıklayarak zip olarak indirebilirler.

İndirme işleminden sonra “Assets” klasörünün içerisine zip dosyasının içerisindekileri kopyalıyoruz. Proje dosyalarını nereye koyduğunuzu hatırlamıyorsanız aşağıdaki gibi “Assets” klasörü üzerinde sağ tık yaptıktan sonra “Show in Explorer” seçerek erişebilirsiniz.

Zemin Objelerinin NavMeshSurface Ayarları
Projemize eklediğimiz NavMeshSurface bileşeni sayesinde oyun sırasında düşman karakterlerinin gideceği yolu hesaplamasını sağlayacağız. Bunun için daha önceden oluşturduğumuz “YolHesaplama” objesinin içerisine iki adet “NavMeshSurface” bileşenini ekleyeceğiz.
“YolHesaplama” objemizi açtıktan sonra “Add Component” butonuna basarak “NavMeshSurface” yazıyoruz. Burada iki farklı düşman tipi için iki farklı harita çıkaracağımız için iki adet “NavMeshSurface” bileşeni ekliyoruz.
GitHub üzerinden indirdiğiniz dosyaları “Assets” klasörünün içerisine attıysanız bileşen gözükecektir. Eğer gözükmüyorsa adımları tekrar kontrol edebilir ya da yorum yazarak destek alabilirsiniz.

Agent Type: Bu alana seçtiğimiz iki farklı ajan türünü ekleyeceğiz. Fakat öncesinde “Navigation/Agents” bölümünden her iki düşman türü için yeni ajanlar eklememiz gerekiyor.
Aşağıda görselde gözüktüğü gibi “Window/Windows/Navigation” menüsüne girdikten sonra sağ tarafta açılan “Navigation” sekmesinin alt sekmesi olan “Agents” sekmesine tıklıyoruz.

İlk açtığınızda varsayılan olarak “Humanoid” ajan tanımlamasını göreceksiniz. Biz burada yer alan “+” simgesine tıklayarak iki adet yeni ajan özellikleri ekleyeceğiz. İsimlerini “MaviBitisDusmanlari” ve “KirmiziBitisDusmanlari” olarak belirledim. Ajan özelliklerini ise her iki ajan içinde aşağıdaki gibi düzenledim. Her iki ajan için şimdilik aynı ayarları yapalım ileriki bölümlerde değişiklikler yapacağız.
Radius: 0.3
Height: 2
Step Height: 0.15
Max Slope: 0
Şimdi eklediğimiz NavMeshSurface bileşeninin “Agent Type” alanlarını her iki bileşende de farklı şekilde değiştirelim.
Include Layers: Sahnede haritayı oluşturacak zemin objesine “Layer” ekledikten sonra eklediğimiz katman ismini “Include Layers” alanından seçiyoruz. Böylece harita hesaplamasını yaparken sadece “Zemin” katmanına sahip objeleri dikkate alması gerektiğini söylemiş oluyoruz.
Navigasyon Haritasının Oluşturulması
Unity Navigation’da olduğu gibi sahnede yer alan tüm objeleri seçtikten sonra “Bake” tuşuna basarak haritayı oluşturabiliriz. Fakat bizim amacımız oyun başladıktan sonra bu işlemi yapması olduğu için arka planda C# ile müdahale ederek yapacağız. Nasıl müdahale edeceğimize geçmeden önce düşman karakterlerimize “Nav Mesh Agent” bileşenini ekleyeceğiz. Böylece “Nav Mesh Surface” ile oluşturduğumuz harita üzerinden ajanlarımızın gitmesini sağlayacağız.
Her iki düşman objemize de “NavMeshAgent” bileşenini ekliyoruz. Mavi düşman için mavi ajan tanımlamasını ve kırmızı düşman için kırmızı ajan tanımlamasını “Agent Type” alanından seçmeniz yeterli olacaktır. Tabi daha sonradan ajan özellikleriyle oynayarak her iki düşman tipini özelleştireceğiz.

Eklemelerden sonra sıra geldi kodumuzu eklemeye. Tüm kodu aşağıda gönderiyorum ve yeni eklediklerimizi kod içinde işaretleyeceğim. Tabi sonrasında da detaylı inceleyeceğiz.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class DusmanDalgalariYoneticisi : MonoBehaviour { public GameObject[] baslangicNoktalari; public GameObject[] zeminler; [SerializeField] private GameObject maviDusman, kirmiziDusman; [SerializeField] private GameObject maviBitisNoktasi, kirmiziBitisNoktasi; private NavMeshSurface[] dusmanyol; private NavMeshAgent yapayZekaMavi, yapayZekaKirmizi; void Start() { baslangicNoktalari = GameObject.FindGameObjectsWithTag("BaslangicNoktalari"); maviBitisNoktasi = GameObject.Find("MaviBitisNoktasi(Clone)"); kirmiziBitisNoktasi = GameObject.Find("KirmiziBitisNoktasi(Clone)"); dusmanyol = transform.GetComponents<NavMeshSurface>(); foreach (var yol in dusmanyol) { yol.BuildNavMesh(); } HaritayiOlustur(); } private void HaritayiOlustur() { for (int i = 0; i < baslangicNoktalari.Length; i++) { if (i == 0) { Vector3 maviBitisFark = maviBitisNoktasi.transform.position - baslangicNoktalari[i].transform.position; GameObject eklenenMaviDusman = Instantiate(maviDusman, baslangicNoktalari[i].transform.position, Quaternion.LookRotation(maviBitisFark, Vector3.up)); yapayZekaMavi = eklenenMaviDusman.GetComponent<NavMeshAgent>(); yapayZekaMavi.SetDestination(maviBitisNoktasi.transform.position); } else if (i == 1) { Vector3 kirmiziBitisFark = kirmiziBitisNoktasi.transform.position - baslangicNoktalari[i].transform.position; GameObject eklenenKirmiziDusman = Instantiate(kirmiziDusman, baslangicNoktalari[i].transform.position, Quaternion.LookRotation(kirmiziBitisFark, Vector3.up)); yapayZekaKirmizi = eklenenKirmiziDusman.GetComponent<NavMeshAgent>(); yapayZekaKirmizi.SetDestination(kirmiziBitisNoktasi.transform.position); } } } }
using UnityEngine.AI;
yazarak Unity AI için gerekli namespace eklemiş oluyoruz. Bu satırı eklemezseniz yapay zeka için olan yapıyı kullanamayız.private NavMeshSurface[] dusmanyol;
ile bir array oluşturduk ve eklediğimiz bileşenleridusmanyol = transform.GetComponents<NavMeshSurface>();
ile bu array içerisine attık. Burada “GetComponents” yazarak birden fazla “NavMeshSurface” bileşenlerini varsa eklemesini söylemiş oluyoruz. “s” takısı ekleyince tüm birden fazla bileşen ekleyebiliyoruz diğer türlü sadece ilk bulduğunu ekleyecektir.- “YolHesaplama” objesine ekli olan “NavMeshSurface” bileşenlerini array yapısına attıktan sonra array içerisinde “foreach” ile gezerek her elemanı için yani her eklediğimiz “NavMeshSurface” bileşeni için
yol.BuildNavMesh();
işlemini yapıyoruz. Burada kullandığımız “BuildNavMesh” fonksiyonu ajanlarımız için haritayı çıkarmaya yaramaktadır. - private NavMeshAgent yapayZekaMavi, yapayZekaKirmizi; yazarak iki adet “NavMeshAgent” türünde obje oluşturduk ve
yapayZekaKirmizi = eklenenKirmiziDusman.GetComponent();
ileyapayZekaMavi = eklenenMaviDusman.GetComponent();
yazarak bu objelere sahneye eklediğimiz düşman objesinin içinde yer alan “NavMeshAgent” bileşenini ekledik. - Son olarak ise düşman karakterleri içinde yer alan “NavMeshAgent” bileşenine
yapayZekaKirmizi.SetDestination(kirmiziBitisNoktasi.transform.position);
veyapayZekaMavi.SetDestination(maviBitisNoktasi.transform.position);
yazarak düşman karakterlerinin hedeflerine doğru hareket etmesini istedik. “SetDestination” fonksiyonu ile belirlediğimiz pozisyona doğru ajanlarımızı yönlendirebiliyoruz.
Engelleri Navigasyon Haritasından Çıkarma
Haritamıza daha önceden eklediğimiz “Engel” objelerinin olduğu alanlarda düşman karakterlerinin geçememesi için “Nav Mesh Obstacle” bileşenini kullanacağız. Bu bileşenle ilgili detaylı bir incelemeyi NavMeshObstacle Bileşeni dersinden öğrenebilirsiniz.
Bileşenin temel görevi eklendiği objeyi düşman karakterlerinin yani ajanların hedefe gitmek için oluşturdukları haritalarından çıkarmak. Böylece düşman karakterleri engellerin üzerinden geçemeyerek etraflarından dolaşacaklar.

Ayarları yukarıdaki gibi yaptıktan sonra navigasyon haritasına bakarsanız dahil olmadıklarını görebilirsiniz.

Eğer adımları başarılı olarak gerçekleştirdiyseniz, oyunu başlattığınızda aşağıdaki gibi bir sonuç elde etmiş olmanız lazım.
Ben farklı bir türde olan hiç yapılmamış bir kule savunma oyunu yapıyom. Lütfen daha fazla yazı yayınlar mısınız?
Hafta sonuna yeni bir bölüm ekleyeceğim. Normal mesaiden vakit kaldığı zamanlarda ekleyebiliyorum maalesef.
yazar abi lütfen bu yazıların devamınıda yazıp yayınlar mısınız?
Oyun yapmak için UEFA lanıyorum
Merhabalar,
Düşman objelerime Navmesh eklememe rağmen şöyle bir hata alıyorum :
“SetDestination” can only be called on an active agent that has been placed on a NavMesh.
Neden Objenin içinde var olan navmesh’i görmüyor?
Selam Aydın, Bu hatayı eğer sahnede yer alması gereken agent olmadığı ya da pozisyonu belirlenmediği zaman çıkıyor. Agent pozisyonunu belirledikten sonra “SetDestination” metodunu çağırmak gerekiyor.