Birlikte öğreniyoruz...

Raycast yani ışın sisteminin oyunlarda aktif olarak kullanıldığı için ayrı bir ders olarak işlemesi gerektiğini düşünerek detaylı bir yazı hazırlamaya karar verdim.

Peki raycast sistemi nedir?

Raycast bir objeden belirlediğimiz yönde ve uzunlukta bir ışın yayabiliriz. Böylece yaydığımız ışının hangi objelere çarptığını tespit edebiliyoruz. Bu sistemi eskiden herkesin elinden düşünmediği lazer sistemi gibi düşünebilirsiniz. Hani şu kedileri oynatmak için kullanılan sistemler gibi.

Bu örnek size yeterli gelmediyse, kafanızda netleştirecek bir örnek vereyim. Counter-Strike ya da benzeri FPS yani nişancı sistemlerinde kullanılan sistem aslında raycast sistemidir.

Silahın ucuna ya da kameraya bir unity raycast sistemi entegre edilerek görünmeyen bir ışın yayar. Bu ışının hizasında bir düşman karakteri varsa ve tetikleme işlemini gerçekleştirirseniz, hedef alına objede istediğiniz işlem gerçekleştir. Işının çarptığı objeyi sahneden sil gibi.

O zaman biraz pratik yaparak, rascast sistemi nasıl kullanılır inceleyelim.

Raycast Kullanımı

Unity Raycast sistemi örnek çalışma

Raycast kullanarak bir ışın atacağız ve ışının herhangi bir objeye değip değmediğini kontrol edeceğimiz bir örnek yapacağız. Eğer adım adım giderseniz elde edeceğimiz sistem yukarıdaki videodaki gibi olacaktır.

Raycast Sahne Tasarımı
Raycast Sahne Tasarımı

İlk olarak raycast yayacak bir obje oluşturdum ve etrafına 4 adet raycast ışınlarının çarpıp çarpmadığını test edeceğimiz objeler ekledim. Böylece silahımızı çevirdikçe ışınların çarptığı objeleri tespit edip, çeşitli işlemler yapacağız.

Bu basit ve şekilsiz sahne ile uğraşmak istemeyen arkadaşlar aşağıdaki bağlantıdan sahneyi indirerek, import edebilirler. 🙂 Tabii ki paket içerisine kullanacağımız kodları eklemiyorum. Yazdığım kodları birebir uygulayarak elinizi alıştırmalısınız. 😀

Raycast Sahne Tasarımı

Sahneyi oluşturduktan sonra “Silah” isimli objemizin alt objesi olan “Nisan” objemiz içine, “RaycastKullanimi” isminde bir C# script dosyası oluşturarak atamasını gerçekleştiriyoruz.

Raycast için C# tarafında kodlarımızı Unity metotlarından Update ya da Start metotları yerine FixedUpdate içerisine koyacağız. FixedUpdate metodu Update metodundan önce çalıştığı ve daha hızlı çalıştığı için bu tercihi yaptık. Unity fonksiyonlar konumuza bakarak ne işe yaradıklarını öğrenebilirsiniz.

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

public class RaycastKullanimi : MonoBehaviour
{

    private void FixedUpdate()
    {
        RaycastHit ates;
        if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.right), out ates, Mathf.Infinity))
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.right) * ates.distance, Color.green);
            Debug.Log("Hedefi Vurduk");
        }
        else
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.right) * ates.distance, Color.red);
            Debug.Log("Hedefi Iskaladık");
        }

    }
}

Burada raycast’ten geri bildirim alabilmek için RaycastHit ates; yazarak “RaycastHit” sınıfından “ates” bir nesne oluşturduk. Sonrasında ise “Physics” sınıfından Raycast metodunu kullanarak belirli bir noktadan belirli bir mesafeye ışın göndereceğiz. Bunun için if bloğu içerisinde “Physics.Raycast” metodunu çağırarak parametrelerini ekliyoruz. Physics.Raycast(transform.position, transform.TransformDirection(Vector3.right), out ates, Mathf.Infinity)

“Physics.Raycast” metoudu bize true ya da false cevabı döneceği için if bloğuna soktuk. Böylece eğer ışınımız sahnede yer alan herhangi bir hedefe denk geliyorsa Console ekranına log bastıracağız.

Merak etmeyin şimdi “Physics.Raycast” sınıfının aldığı parametreleri inceleyeceğiz.

  • İlk parametreye transform.position yazarak ışının başlangıç noktasını belirtmiş oluyoruz. Script hangi objemize bağlı ise, o objenin pozisyonundan başlayacak.
  • İkinci parametre transform.TransformDirection(Vector3.right) yazarak ise ışının hangi yönde hareket edeceğini belirtiyoruz.
  • Üçüncü parametre yani out.ates olarak yazan kısımda ise RaycastHit sınıfından oluşturduğumuz objenin ismini veriyoruz. Bu kısım, ışının herhangi bir objeye çarpıp çarpmadığını kontrol etmemize yaramaktadır.
  • Dördüncü parametrede ise gönderilecek olan ışının mesafesini belirledik. Mathf.Infinity yazarak sonsuz olmasını sağladık fakat belirli bir mesafe de verebiliriz.

Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.right) * ates.distance, Color.green);
Debug.Log("Hedefi Vurduk");

İf ve else içerisinde yukarıdaki Debug kodlarını göreceksiniz. Bu kodlar sayesinde sahnede hedefe doğru giden kırmızı ve yeşil lazerler görebileceğiz. Ayrıca console ekranında objeye değip, değmediği bilgisini alacağız.

Bu arada unutmayın, oyun ekranında bu lazerler gözükmeyecektir sadece sahne tasarımı ekranında.

Unity Raycast Sisteminde LayerMask Kullanımı

Raycast Sistemi LayerMask Kullanımı

Unity Raycast sistemi ile yaydığımız ışınları belirli objeleri dikkate almamasını sağlamak için LayerMask sistemini kullanabiliriz. Yani şöyle düşünün, sahnede yer alan iki objeniz var ama gönderdiğiniz ışının birine çarpmasını ama diğerine çarpmamasını istediğinizi düşünün.

Layer sisteminin ne olduğunu bilmiyorsanız yani önceki derslerimi atlayarak bu derse geçerek kolaya kaçtıysanız 😀 Unity Objeler konumuza göz atabilirsiniz.

LayerMask kullanımını anlamamız için aşağıdaki gibi bir sahne tasarımı yaptım. Duvarın arkasında bir objemiz bulunuyor. Yine aynı silahımızdan ışın yayacağız ama duvarı pas geçerek direk objemize çarpacak.

Raycast Sisteminde LayerMask Kullanımı
Unity Raycast Sisteminde LayerMask Kullanımı

Sahne içinde mavi renkli olan objemize yeni bir katman yani layer ekleyerek “Dusman” ismini verdik. Eklediğimiz katmanın sırası ise 8 gözüküyor.

Raycast için yeni katman ekleme.
Raycast için yeni katman ekleme.

Sahne tasarımını indirmek için bu bağlantıyı kullanabilirsiniz.

Burada yaptığımız tek değişiklik “layerMask” isminde bir değişken oluşturarak Physics.Raycast metoduna parametre olarak eklemek oldu.

Yani int layerMask = 1 << 8; yazarak, raycast ışınlarının sadece 8 numaralı katmanı yani “Dusman” katmanını hedef alması için int bir değişken oluşturuyoruz. Bu tanımlama ile sadece “Dusman” katmanı eklenmiş olan objeleri hedef olarak göreceğiz.

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

public class RaycastLayerMaskKullanimi : MonoBehaviour
{



    private void FixedUpdate()
    {

        int layerMask = 1 << 8;
        RaycastHit ates;


        if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.right), out ates, Mathf.Infinity, layerMask))
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.right) * 1000, Color.green);
            Debug.Log(ates.transform.name + " Hedefi Bulundu");
        }
        else
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.right) * 1000, Color.red);

            Debug.Log("Hedef Bulunamadı");
        }

    }
}

Ayrıca tam tersi şekilde yani belirli bir katman haricinde diğer katmanlara ışın çarpabilsin isterseniz aşağıdaki gibi yazabilirsiniz.

int layerMask = 1 << 8;
layerMask = ~layerMask;

Evet şimdi denklemi biraz zorlaştıracak bir örnek yapacağız.

Unity Raycast Sistemini FPS Oyunlarda Kullanmak

Şimdi bir FPS(First Person Shooter) oyunu mantığıyla elimizde bir silah olacak ve silahtan çıkan Rasycast ışınlarını mermi gibi düzenleyerek ateş etmesini sağlayacağız.

Amacımız sahnede yer alan hedefleri raycast ışınıyla vurarak renklerini değiştirmek ve en sonunda sahneden silmek olacak. Bunu oyundaki bir karakterin canının azalarak bitmesi ve ölmesi olarak düşünebilirsiniz.

Örnek çalışmanın tamamlanmış hali aşağıdaki gibi olacak.

Raycast FPS Örnek Çalışma

Şimdi bu tarz bir çalışma yapabilmek için ihtiyacaımız olanlar aşağıdakiler.

  1. Sahne tasarımını hazırlamak.
  2. Oyunu FPS türüne çevirmek. (Kamera, Character Controller ayarları vb)
  3. Mouse hareketlerini oluşturmak. (Mouse ile karakteri sağa sola döndürmeye çalışma)
  4. Klavye girdilerini alabilmek. (W,A,S,D tuşlarını aktif etmek)
  5. Silah tasarımını oluşturmak.
  6. Nişan alabilmemiz için bir görsel oluşturmak.
  7. Raycast ile ateş etme mekanizmasını kurmak.

Şimdi bu dersimize kadar görmediğimiz konular olduğu için sadece Raycast ile ateş etme kısmını beraber yapacağız. Ama merak etmeyin örneğin klavye ve mouse hareketlerini algılama dersimiz, bir sonraki ders olacağı için çokça örnek yapacağız.

Tabi yine de yapım aşamasıyla ilgili bir kaç bilgi vereyim.

  • Sahne tasarımı için “ProBuilder” ve “ProGrids” paketlerini kullandım. Sahneyi aktarmadan önce “Window/Package Manager” menüsünden “ProBuilder” olarak arama yaparak sisteminize import etmeniz gerekiyor. Eğer import etmezseniz sahne tasarımı sorunlu gözükecektir. Paketi yükledikten sonra import yaparsanız herhangi bir sorun yaşamazsınız.
  • FPS için kamera ve Character Controller bileşeninin ayarlarını gerçekleştirerek. Klavye ve mouse üzerinden kontrol için gerekli C# kodlarını ekledim.
  • Silah tasarımı için bu adresteki, “stylized 3d” gun isimli ücretsiz asseti kullandım.

Şimdi aşağıdaki adresteki sahne tasarımını indirin ve “Assets > Import Package > Custom Package.. ” kısmından import yapın. Import işleminden sonra kodlama kısmını beraber yapıyoruz.

Raycast ile FPS Sahne Tasarımı

Sahne tasarımı, silah vs için basit bir çalışma yaptım. Amacımız mantığı anlamak 🙂

Bu paket ile elinizde sahne tasarımı, hareket edebilen bir karakter ve silah ile nişan sisteminin olması gerekiyor. Şimdi bizim yapacağımız kısma gelelim.

İlk önce “Hierarchy” bölümünden “Karakter > Main Camera > Silah > RaycastUygulanacakKisim” isimli objemizi seçiyoruz ve içerisine “RaycastAtisSistemi” isminde bir C# script oluşturarak, ekliyoruz.

Kod kısmına yazacaklarını aşağıdaki gibi olacak. Kodu alıp kaçmayın. 🙂 Daha yapacak işlerimiz var ve tek tek kodu basamaklandırmaya çalışarak anlatacağım. 🙂

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

public class RaycastAtes : MonoBehaviour
{
    public float mesafe = 85f;
    public Camera kamera;
    private Renderer obje;
    public Material[] renkler;

    private void Update()
    {
        if (Input.GetButtonDown("Fire1"))
        {

            AtesEt();
        }

        void AtesEt()
        {
            RaycastHit hit;
            int katman = 1 << 8;

            if (Physics.Raycast(kamera.transform.position, kamera.transform.TransformDirection(Vector3.forward), out hit, mesafe, katman))
            {
                obje = hit.transform.GetComponent<Renderer>();

                RenkDegistir();
            }
            else
            {
                Debug.Log("Hedefi Iskaladık");
            }
        }
        void RenkDegistir()
        {
            
            switch (obje.material.name)
            {
                case "ProBuilderDefault (Instance)":
                    obje.material = renkler[0];
                    break;
                case "YesilRenk (Instance)":
                    obje.material = renkler[1];
                    break;
                case "TuruncuRenk (Instance)":
                    obje.material = renkler[2];
                    break;
                case "KirmiziRenk (Instance)":
                    Destroy(obje.transform.gameObject);
                    break;
            }
        }
    }
}    
  • public float mesafe = 85f; – Işının ne kadar mesafeye gideceğini belirlemek için oluşturduğumuz Float türünde değişken oluşturduk.
  • public Camera kamera; – Işının başlayacağı ve gideceği yönün seçiminde kamerayı kullanacağımız için Camera sınıfından obje oluşturduk.
  • private Renderer obje; – Işın ile vurduğumuz objenin renklerini değiştirmek için ekliyoruz.
  • public Material[] renkler; – Raycast ile hedefi her vurduğumuzda değişecek olan renkleri tutmamız için eklediğimiz Material türünde array.
  • Input.GetButtonDown("Fire1") – Mouse’nin sol tuşuna basılırsa if bloğuna girecek ve AtesEt() metodunu çağıracaktır.

Oyuncu eğer ateş tuşuna basarsa aşağıdaki metot çalışacak.

void AtesEt()
        {
            RaycastHit hit;
            int katman = 1 << 8;

            if (Physics.Raycast(kamera.transform.position, kamera.transform.TransformDirection(Vector3.forward), out hit, mesafe, katman))
            {
                obje = hit.transform.GetComponent<Renderer>();

                RenkDegistir();

            }
            else
            {
                Debug.Log("Hedefi Iskaladık");
            }
        }
  • RaycastHit hit; – Raycast ışının çarptığı objelerin bilgilerini almak için oluşturduk.
  • int katman = 1 << 8; – Sadece 8 numaralı katmana sahip olan objelere ateş edilebilmesi sınırlama koyduk. Sahnede yer alan tüm hedef objeleri “Hedefler” isminde bir layer yani katmana sahip.
  • if (Physics.Raycast(kamera.transform.position, kamera.transform.TransformDirection(Vector3.forward), out hit, mesafe, katman)) – Burada Raycast ışınını yaymak için “Physics” sınıfından “Raycast” metodunu kullanıyoruz.

Burada yazılan parametrelerden kamera.transform.position ile ışının başlayacağı poziyonu belirledik. kamera.transform.TransformDirection(Vector3.forward) yazarak ise hangi yöne doğru gideceğini belirledik.

Işının çarptığı objelerin bilgisini almak için zorunlu “out” anahtar kelimesiyle oluşturduğumuz objeyi ekledikten sonra ışının gideceği mesafeyi belirtmek için “mesafe” değişkenini ve hangi katmanlarda işe yarayacağını belirtmek içinde “katman” değişkenini ekliyoruz.

  • obje = hit.transform.GetComponent<Renderer>(); – Oluşturduğumuz obje nesnesine, rengini değiştirme işlemini yapabilmek için “Renderer” bileşenini atadık.
  • RenkDegistir(); – Renk değişrme işlemleri için ayrı bir metot oluşturduk.

Şimdiye kadar silahımızdan bir ışın yayarak belirlediğimiz objelere çarpıp çarpmadığını bulduk. Şimdi ise renklerini değiştireceğiz.

Bu kısım için, kendim bir mantık kurarak Switch-Case yapısını kullandım. Bu yapıyı daha önceki derslerimizde görmüştük isterseniz aşağıdaki konudan tekrar göz atabilirsiniz.

C# Switch Case Yapısı

Sahnede yer alan objelerde varsayılan olarak ProBuilder paketinden gelen “ProBuilderDefault (Instance)” isminde bir material yer aldığını gördükten sonra switch (obje.material.name) yazarak her atış sonrası ışının çarptığı objenin material ismini çekerek Switch-Case ile kontrol edecek bir yapı oluşturmuş oluyoruz.

Böylece hedefin rengi yeşile döndüyse yani material olarak “YesilRenk (Instance)” bulunuyorsa obje.material = renkler[1]; ile objeye Renkler isimli Array dizisinin 2 numaralı index sırasında yer alan “TuruncuRenk (Instance)” material atamasını yapıyorum. Diğer renkler içinde aynı mantıkta işliyor. Sadece son renk “Kırmızı” olduğu zaman Destroy(obje.transform.gameObject); yazarak hedefi sahneden silmiş oluyoruz.

Amacımız bir hedef vurulduğunda sırasıyla yeşil, turuncu ve kırmızı renklere geçtikten sonra otomatik olarak sahneden silinmesi olduğu için en uygun yöntem bu gibi geldi ama yinede daha iyi bir yöntem bulanlar yazabilirler.

C# kodunu kaydettikten sonra “Public” olarak eklenen değişkenlere tanımlamaları aşağıdaki gibi eklemeyi unutmayın. Renk materyalleri yüklediğiniz paketin içinde olduğu için sadece seçmeniz yeterli olacaktır.

Raycast Public Değişkenler
Raycast Public Değişkenler

Unity Raycast sistemi için bir FPS örneğini tamamladık. Bence eğer bu yoğun dönemde kafamın karışıklığından dolayı çok saçma yazmadıyam anlamış olmanız gerekiyor diye düşünüyorum. En azından ben yazarken tekrar öğrendim.

Bir sonraki dersimizde klavye, mouse, joystick, dokunmatik ekran ile yön verme işlemlerini yani Input konusuna değineceğim. Beklemede kalın…

Bir sonraki dersimiz hazır aşağıdaki bağlantıdan erişebilirsiniz.