Unity Character Controller, Rigidbody ile uğraşmadan etraftaki objelerle çarpışmaya sadık kalacak şekilde karakterlerimizi hareket ettirmemizi sağlayan bir bileşendir. Temelde böyle bir bileşene ihtiyaç duyma nedenimiz de her ne kadar gerçekçi hareketlere sahip bir kontrol mekaniği istesek de gerçekçiliğin tümünün bu kontrol mekaniğinde olmasını istemeyiz.

Örneğin klasik Doom-benzeri fps oyunlarını ele alalım. Karakterimiz aşırı hızlı hareket eder, aniden yön değiştirir ve aniden durur. Rigidbody temelli bir sistemde bunu başarmak ciddi manada uğraş gerektirir ve başarıldığında bile doğru şekilde hissettirmez. Unity Character Controller ise tam olarak bu ihtiyaçlara cevap verecek şekilde oluşturulmuştur. Etraftaki objelerin içinden geçmeden, istediğimiz şekilde karakteri hareket ettirmemizi sağlar.

Ayrıca, direkt olarak fizik sistemi aracılığıyla Rigidbody objelerin birbirine kuvvet uygulamasının aksine, Character Controller bundan etkilenmez. Rigidbody ile hareket ettirilen bir merminin, yüksek hızlarda başka bir rigidbody bileşene çarptığını düşünün. Ne kadar ağır olsa da diğer obje bu kuvvetten etkilenecek ve kontrolümüz dışında hareket edecektir. Mermiyi trigger halde kullanmak da bir çözüm tabii, ama sadece mermiler kuvvet uygulamıyor sonuçta 🙂 Bunun dışında olan örnek olaylarda da, kaçınmak istediğimiz temel şey, dışarıdan bir kuvvetin hareketimizi bozmasıdır. Rigidbody bileşeninde bu kontrolleri sağlamak bizim için ayrı bir iş yükü oluşturacaktır.

Tabii bu korunma etkisiyle, aynı şekilde Character Controller diğer rigidbody objelere kuvvet uygulayamaz. Yani temel haliyle diğer objeleri itemez, hareket ettiremez. Yazının devamında da örneğini bulacağınız şekilde, isterseniz bu özelliği OnCharacterColliderHit mesajı ile temas eden objelere elle kuvvet uygulama şeklinde gerçekleştirebilirsiniz.  

Tabii tam tersi şekilde, dış kuvvetlerden, diğer rigidbody objelerden otomatik olarak etkilenen bir karakteriniz olsun isteyebilirsiniz. O durumda Unity Rigidbody bileşeninden devam etmekte fayda var. 

Ayrıca hatırlatmakta fayda var, Unity’nin kendi yayınladığı en son karakter kontrolcüsü paketlerinde de Unity Character Controller kullanılıyor. Daha önce hazır sunulan pakette Rigidbody kullanılıyordu. Bu yeni kontrolcüler hakkında daha fazla bilgi edinmek isterseniz “Unity Starter Assets Yayınlandı” yazımıza göz atabilirsiniz.

Character Controller, kapsül şeklinde bir collider’a sahiptir. Bu sayede diğer objelerle olan çarpışmalardan haberdar olabilir. Bileşen üzerinden bu kapsülün yüksekliğini ve genişliğini belirlememiz mümkündür. Ayrıca ne kadar eğimli yüzeylerde dolanabilir, sahip olduğu collider merkez noktası nerede olmalıdır gibi çeşitli ayarları da yapabiliriz. Editör üzerinden yapabildiğimiz bu ayarları, aynı şekilde kod kısmından da oyun esnasında düzenlememiz mümkündür. Bunun için öncelikle Character Controller Özelliklerini inceleyelim.

Unity Character Controller Bileşeni
Unity Character Controller Bileşeni

Character Controller Özellikleri

Character Controller Özellikleri bileşeninin yapısını, etkileşim ayarlarını ve etkileşim durumlarına dair bilgileri tutan değişkenleri içerir. Bunların bazılarını anlık olarak değiştirirken, örneğin karakterin boyunu kısaltabiliriz, bazılarını sadece okuyabiliriz, örneğin karakter yere temas ediyor mu bilgisini elde edebiliriz. Her başlık altında örnek kod olmasa da, çoğu özelliği içeren örnek kodları da ekledim. Kodlar içerisindeki yorum satırları ile işleyişi daha iyi anlayabileceğinizi umuyorum.

center: Character Controller bileşenine ait olan kapsül konumunu belirlemizi, okumamızı sağlayan değişkendir. Vector3 cinsinden bir değerdir.

collisionFlags: Character Controller bileşenine ait kapsül ile yapılan diğer objeye temas durumlarında, karakterin hangi bölgesine (aşağısı, yukarısı, yanları) temas edildiği bilgisine erişmemizi sağlar. CollisionFlags enum tipinde değer döndürür.

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

//RequireComponent içerisinde CharacterController tipini belirtiyoruz, 
//böylece bu scripti atadığımız her objede Character Controller olduğundan emin olabiliriz, otomatik olarak eklenir.
[RequireComponent(typeof(CharacterController))]
public class KaradotCCCollisionFlags : MonoBehaviour
{
	
    private CharacterController controller;
   
	//Başlangıçta bileşenimize erişiyoruz ve değişkenimize alıyoruz.
    private void Start()
    {
        controller = GetComponent<CharacterController>();
    }

     void Update()
    {
      
        if (controller.collisionFlags == CollisionFlags.None)
        {
            print("Havada süzülüyor, temas yok!");
        }

        if ((controller.collisionFlags & CollisionFlags.Sides) != 0)
        {
            print("Etrafından bir şeylere temas ediyor");
        }

        if (controller.collisionFlags == CollisionFlags.Sides)
        {
            print("Yalnızca yanlardan temas var,başka bir temas yok");
        }

        if ((controller.collisionFlags & CollisionFlags.Above) != 0)
        {
            print("Tavana değiyor");
        }

        if (controller.collisionFlags == CollisionFlags.Above)
        {
            print("Sadece yukardıan temas,başka bir temas yok");
        }

        if ((controller.collisionFlags & CollisionFlags.Below) != 0)
        {
            print("Yere değiyor");
        }

        if (controller.collisionFlags == CollisionFlags.Below)
        {
            print("Yalnızca yere değiyor, başka bir temas yok");
        }
    }
}

detectCollisions: Collider ve diğer character controller bileşenine sahip objelerle olan temasların algılanıp algılanmayacağını belirtmemizi sağlayan değişkendir. Standart olarak sahip olduğu değer true değeridir. Yani standart olarak her teması algılar. 

enableOverlapRecovery: Diğer objelerle iç içe geçme durumunun otomatik olarak engellenip engellenmeyeceğini belirttiğimiz değişkendir. Bu koruma sistemi standart olarak aktiftir, yani bu değişken true değerine sahiptir. Böylece diğer objelerin içinde kalma, sıkışma gibi durumlar otomatik olarak engellenir. 

height: Karakter kapsülünün yüksekliğini tutan ve değiştirmemize imkan sağlayan değişkendir. Bunu değiştirerek karakter modeline uygun yüksekliğe getirebilirsiniz veya karakter eğildiğinde de eğilme boyutuna göre ayarlayıp, daha dar alanlardan geçmesini sağlayabilirsiniz. 

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

//RequireComponent içerisinde CharacterController tipini belirtiyoruz, 
//böylece bu scripti atadığımız her objede Character Controller olduğundan emin olabiliriz, otomatik olarak eklenir.

[RequireComponent (typeof (CharacterController))]
public class KaradotCCCrouch : MonoBehaviour {

    private CharacterController controller;

    bool isCrouching = false;

    //Başlangıçta bileşenimize erişiyoruz ve değişkenimize alıyoruz.
    private void Start () {
        controller = GetComponent<CharacterController> ();
    }

    void Update () {
        if (Input.GetKeyDown (KeyCode.LeftControl)) {
            ChangeCrouch ();
        }
    }
    //Uyarmakta fayda var, bunu yaptığımızda merkeze doğru yükselik değişmekte
    //Yani bir kapsül düşünürsek, üstten ve alttan azalacaktır boyutunu kısalttığımızda
    //Bunu önlemek adına center değerini de objenin yere değecek kısmına göre ayarlamakta fayda var.
    //Örnek verdiğim kapsül objesinde bu kod işimize yarayacaktır. Görsel olarak olmasa da collider üzerinden bunu görebilir
    //eğilince daha kısa yerlerden geçebiliriz. 
    void ChangeCrouch () {
        isCrouching = !isCrouching;
        if (isCrouching) {
            controller.height = 1.5f;
            controller.center = new Vector3 (0, -.25f, 0);
        } else {
            controller.height = 2;
            controller.center = Vector3.zero;
        }
    }
}

isGrounded: Karakterin yere teması edip etmediği bilgisini elde etmemizi sağlayan değişkendir. Bool tipinde değer döndürür.

minMoveDistance: Karakterin hareket edebileceğini minimum mesafeye erişebileceğimiz ve bu mesafeyi değiştirebileceğimiz değişkendir. Karakter belirtilen mesafenin altındaki hareketleri gerçekleştirmeyecektir. Float cinsinde bir değişkendir.

radius: Karaktere ait kapsülün genişliğini tutan ve bu genişliği değiştirmemizi sağlayan değişkendir. Float cinsinde bir değişkendir.

skinWidth: Karaktere ait kapsülün etrafında oluşturulacak ve temasları algılamayı sağlayacak alanın kalınlığı. Birebir çevirirsek aslında deri kalınlığı gibi de oluyor ama, kafa karıştırmayalım 🙂 Normalde oldukça ufak tutmak gerekir, tabii oyununuzda geçerli olan boyutlar büyürse bunu da büyütmeniz gerekebilir. Float cinsinden bir değişkendir.

slopeLimit: Karakterin hareket edebileceği eğimli yüzeyler için maksimum eğim değeri. Açı cinsinden belirtilir. Basit bir örnekle 30 derece olarak ayarlarsanız, karakteriniz yalnızca 30 derece altındaki eğime sahip yüzeylerde hareket edebilir. Float cinsinden bir değişkendir.

stepOffset: Karakterin varolan zeminden daha yukarı çıkabileceği, adım atabileceği maksimum yüksekliği belirtir. Basit bir örnekle, eğer bu değer 2 ise ve 1 birimlik basamaklara sahip bir merdiveniniz varsa o merdiven üzerinde ilerleyebilirsiniz. Ancak 5 metrelik bir duvarın üstüne çıkmasını böylece engellemiş oluruz. Tabii basamak değerleri bu kadar yüksek olmaz normalde 🙂 Daha önce bahsettiğimiz height değerinden yüksek bir değer vermemeniz önemli, yoksa hata ile karşılaşabilirsiniz. Float cinsinden değer vermemiz gerekir.

velocity: Karakterimizin anlık konum değişim, hareketini Vector3 cinsinden elde etmemizi sağlayan değişkendir.

Character Controller Fonksiyonları

Unity Character Controller fonksiyonları ile karakterimizi 2 farklı şekilde hareket ettirebiliriz. 

Move: Karakterimizi belirttiğimiz anlık hareket değerine göre hareket ettirir. Yani anlık olarak, uyguladığımız karede ne kadarlık bir konum değişimi, hareket gerçekleşmesini istiyorsak bunu değişken olarak vermemiz gerekir. 

SimpleMove: Karakteri Vector3 cinsinde belirttiğimiz hızda hareket ettirmemizi sağlar. Ancak verdiğimiz Vector3 değişkeninin y değeri yani dikeyde yapılacak hareketi görmezden gelir. Ayrıca otomatik olarak karaterimize yerçekimi uygular. 

using UnityEngine;

//RequireComponent içerisinde CharacterController tipini belirtiyoruz, 
//böylece bu scripti atadığımız her objede Character Controller olduğundan emin olabiliriz, otomatik olarak eklenir.
[RequireComponent (typeof (CharacterController))]
public class KaradotCCMove : MonoBehaviour {
    //Karakterimizin CharacterController bileşeni, anlık hızı, yere temas edip etmeme durumu gibi verileri tutmak için değişkenlerimizi oluşturuyoruz.
    [SerializeField]
    private CharacterController controller;
    [SerializeField] private Vector3 playerVelocity;
    [SerializeField] private bool groundedPlayer;
    [SerializeField] private float playerSpeed = 2.0f, rotateSpeed = 2f;
    [SerializeField] private float jumpHeight = 1.0f;
    [SerializeField] private float gravityValue = -9.81f;

    [SerializeField]
    bool isSimpleMove = false;

    //Başlangıçta bileşenimize erişiyoruz ve değişkenimize alıyoruz.
    private void Start () {
        controller = GetComponent<CharacterController> ();
    }

    void Update () {

        //editor üzerinden rahat geçiş yapabilmek adına, bool tipinde bir değişken ile hangi tipte hareket istediğimiz kontrol edip
        //ona uygun fonksiyonları çağırıyoruz.
        //Normalde sadece birini kullanacağımız için böyle bir kontrol eklemeye gerek yok, tamamen ikisini görebilmek adına
        //kapatıp açabileceğimiz bir işlevsellik eklemek istedim.
        if (isSimpleMove) {
            SimpleMoveFunc ();
        } else {
            MoveFunc ();
        }

    }

    //Move fonksiyonu ile yapılan hareket, içerisinde zıplama da bulunuyor.
    void MoveFunc () {
        //Controller içerisinde isGrounded seçeneğine erişiyoruz ve değişkenimize alıyoruz.
        groundedPlayer = controller.isGrounded;
        //Eğer karakterimiz yerde ise ve y eksenindeki hareketi, yani dikeyde yaptığı hareketi negatif değere sahipse bunu sıfırlıyoruz.
        //Çünkü zaten yerde olduğu için içerisinden geçemeyecek, bu değeri tutarak aşağı doğru hareket ettirmeye çalışmamıza gerek yok.
        if (groundedPlayer && playerVelocity.y < 0) {
            playerVelocity.y = 0f;
        }

        //Horizontal ve Vertical axis değerlerini okuyarak kullanıcının verdiği girdiye göre bir Vector3 değişken oluşturuyoruz.
        //Bu temelde hareketimizin nasıl olacağını bize belirtmekte. Yalnızca x ve z eksenlerine değer aldığımıza dikkat edin.
        //Global yani genel sahne koordinat sisteminde yalnızca ileri geri veya sağa sola hareket ettirmek için değer oluşturuyoruz.
        Vector3 move = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
        //Move fonksiyonu ile hareket gerçekleştiriyoruz. Burada dikkat etmemiz gereken şey
        //Time.deltaTime değeri. Bu değer bize bir önceki kare(frame) ile şu anki arasındaki zaman farkını verecektir.
        //Bizim oluşturduğumuz vektör 1 saniyede yapılacak hareketi temsil ediyor diyebiliriz, 
        //anlık kare farkı ile çaprtığımızda, ufak bir anda ne kadarlık bir hareketi gerçekleştireceğini elde etmiş oluyoruz.
        controller.Move (move * Time.deltaTime * playerSpeed);

        //Eğer herhangi bir hareket gerçekleştiriliyorsa, kullanıcı girdi oluşturduysa
        //karakterin ileri yönünü harekete eşitliyoruz. Yani karakterimiz hareket ettiğimiz yöne bakıyor.
        if (move != Vector3.zero) {
            gameObject.transform.forward = move;
        }

        //Burada karakterimiz yerdeyken "Jump" butonuna basılması durumunda, ki klavyede space tuşuna denk gelir,
        //Karakterimizin hız değerini değiştiriyoruz. Dikkat ederseniz yalnızca y eksenindeki değeri değiştiriyoruz, 
        //böylece yukarı yönlü bir hareket değeri elde etmiş oluyoruz.
        if (Input.GetButtonDown ("Jump") && groundedPlayer) {
            //Burada y değerine formül ile karşı koymamız gereken yer çekimi kuvvetini ve istedğimiz yüksekliği içeriyor.
            //ortadaki değerin negatif olmasının sebebi, aslında gravity değişkeninin tersini elde etmek ile alakalı. Direkt olarak
            //gravityValue başına da - işareti koyabilirsiniz.
            playerVelocity.y += Mathf.Sqrt (jumpHeight * -3.0f * gravityValue);
        }

        //Her kare(frame) içerisinde karakterimize yerçekimi uyguluyoruz ve yer çekimini uygulamak için hareket ettiriyoruz.
        playerVelocity.y += gravityValue * Time.deltaTime;
        controller.Move (playerVelocity * Time.deltaTime);
    }

    void SimpleMoveFunc () {
        // A-D tuşlarından ("Horizontal" input axis değerinden) gelen girdiye göre karakterimizi sağa sola çeviriyoruz.
        transform.Rotate (0, Input.GetAxis ("Horizontal") * rotateSpeed, 0);

        //Objemizin ileri yön vektörünü elde ediyoruz
        Vector3 forward = transform.TransformDirection (Vector3.forward);
        //  W-S tuşlarından ("Horizontal" input axis değerinden) gelen girdiler ile karakterimizin ileri veya geri hareket hızını belirleyip değişkene atıyoruz
        float curSpeed = playerSpeed * Input.GetAxis ("Vertical");
        //SimpleMove fonksiyonu aracılığıyla yön vektörümüz ve hız değerimizi çarparak ne kadar hızlı ve hangi yönde ilerleyeceğini beirtiyoruz.
        controller.SimpleMove (forward * curSpeed);
    }
}

Character Controller Mesajları

Character Controller mesajları arasında aslında tek mesaj bulunmakta. O da OnCharacterControllerHit mesajıdır.

OnControllerColliderHit Karakterimiz hareket ederken başka bir obje ile temas ettiğinde çalışır. Aynı collider mesajlarında olduğu gibi düşünebilirsiniz. 

using UnityEngine;

public class KaradotCCSimpleRigidbodyPush : MonoBehaviour {
    // Bu script sayesinde Character Controller kapsülüne temas eden her Rigidbody bileşenine sahip objeye kuvvet uygulanacak ve itilecektir
    [SerializeField] float pushPower = 2.0f;
    void OnControllerColliderHit (ControllerColliderHit hit) {
        Rigidbody body = hit.collider.attachedRigidbody;

        // Temas edilen objede rigidbody yoksa veya kinematic halde ise hiçbir şey yapmadan geri dönüyoruz
        if (body == null || body.isKinematic) {
            return;
        }

        // hareketimiz aşağı yönlü ise geri dönüyoruz, çünkü objeleri aşağı doğru itmek istemeyiz
        if (hit.moveDirection.y < -0.3) {
            return;
        }

        // moveDirection değişkeni ile hareket değerimizi elde ediyoruz
        // ve yalnızca x, z değerlerini alıyoruz ki objeleri yukarı veya aşağı doğru itmeyelim.
        Vector3 pushDir = new Vector3 (hit.moveDirection.x, 0, hit.moveDirection.z);
        //İsterseniz ayrıca karakterin hızını alıp, magnitude değerine göre kuvvetin şiddetini de ayarlayabilirsiniz.

        // Ve son adım olarak temas edilen rigidbody objesinin velocity değerini değiştiriyoruz.
        body.velocity = pushDir * pushPower;
    }
}

Unity Character Controller yazımızın sonuna gelmiş olduk böylece. Her ne kadar Türkçe çeviri halinde bu yazıları oluşturuyor ve sizinle paylaşıyor olsak da, dokümanı orjinal dilinde incelemek isterseniz buraya tıklamanız yeterli.

2 YORUMLAR

  1. Character Controller kullandığımız bir objeyi sadece collideri olan ama rigidbodysi olmayan bir nesneye çarptırıp o nesne ile etkileşime girmesi mümkün mü acaba ?

    • Merhaba, en basit hali o durumda yine Rigidbody kullanılması açıkçası. Ha yer çekimi umrumda değil, örneğin bir kübe dokunuyorum ve dokunduğum taraftan itiyormuşum gibi olsun istiyorsan tabii ki bu tarz basit işlemleri yapabilirsin. Kendi yazacağın bir basit script OnCollisionEnter üzerinden teması dinler, sonrasında hareket kodunu çağırır. https://karadotgames.com/unity-coroutine-nedir/ linkini incelersen, belirli bir sürede hedef konuma bir objeyi nasıl götürebileceğini anlatıyordum “Unity Coroutine Kullanımı” başlığındaki örnekte. Onun üzerinden düzenlemeler yapıp ilerleyebileceğini düşünüyorum. Orada basitçe space tuşuna basıldığında daha önceden belirlediğim belirli bir konuma gidiyordu obje. Bunun yerine gitmesini istediğin konumu hesaplaman gerekecek tabii. Geç cevap verdiğim için kusura bakma.

CEVAP VER

Buraya yorumunuzu ekleyin
Buraya adınızı girin