Yazılım geliştirme dünyasında, karmaşık sistemlerin etkili bir şekilde yönetilebilmesi için çeşitli tasarım desenleri kullanılır. Bu desenlerden biri olan "State Machine Design Pattern" ya da Türkçe adıyla "Durum Makinesi Tasarım Deseni" bir nesnenin durum değişikliklerini modellemek ve bu durum değişikliklerine göre farklı davranışları tetiklemek için kullanılmaktadır.

Her şeyden önce bu yazım, oyun programlama dünyasına yeni adım atmış olan arkadaşlarımıza nereden başlamaları gerektiğine dair ışık tutması ve State Machine temel kavramlarından bahsederek nasıl çalıştığını ve bir örnek üzerinden nasıl uygulandığını anlatacağım.

State Machine Nedir?

State Machine, bir nesnenin içsel durumlarını ve bu durumların geçişlerini temsil eden bir modeldir. Her durum, nesnenin belirli bir zaman diliminde sahip olduğu özellikler ve davranışlarla ilişkilidir. State Machine, bir nesnenin durumlarını bir dizi tanımlanmış durum arasında geçiş yapabilen bir mekanizma ile yönetir.

State Machine Design Pattern:

State Macgine Design Pattern, bir nesnenin durumlarını temsil etmek ve bu durumlar arasındaki geçişleri kontrol etmek için kullanılır. Ana amacı, bir nesnenin davranışlarını durumlarına bağlı olarak değiştirmek ve bu değişimleri yönetmektir.

Nasıl Çalışır?

State Machine Design Pattern genellikle birkaç ana bileşenden oluşur:

  1. Durumlar (States): Bir nesnenin sahip olabileceği farklı durumları temsil eder. Örneğin, bir lambanın durumları "Açık" ve "Kapalı" olabilir.
  2. Geçişler (Transitions): Durumlar arasındaki geçişleri belirler. Örneğin, lambanın durumu "Kapalı" iken kullanıcının lambayı açma isteği, durumu "Açık" olarak değiştirebilir.
  3. Eylemler (Actions): Her duruma geçildiğinde gerçekleştirilecek özel eylemleri temsil eder. Örneğin, lambanın durumu "Açık" olduğunda ışığı yakma eylemi gerçekleştirilebilir.

Şimdi bir oyun senaryosunun ele alarak örnek verelim;
  1. Durumlar:
    • Normal Durum: Oyuncu normalde dolaşıyor, diğer karakterlerle konuşuyor ve eşyaları topluyor.
    • Savaş Durumu: Kahraman düşmanlarla karşılaştığında bu duruma geçer, savaşa odaklanır.
    • Güvenli Durum: Kahraman bir şehre ulaştığında veya güvenli bir bölgeye girdiğinde bu duruma geçer, dinlenir ve iyileşir.
  2. Geçişler:
    • Normal Durum → Savaş Durumu: Kahraman bir düşmanla karşılaştığında.
    • Savaş Durumu → Normal Durum: Savaş sona erdiğinde.
    • Normal Durum → Güvenli Durum: Şehre ulaşıldığında.
    • Güvenli Durum → Normal Durum: Şehirden ayrıldığında.
  3. Eylemler:
    • Savaş Durumu'nda: Düşmanlara saldırma, savunma yapma, iyileşme eylemleri.
    • Normal Durum'da: Eşyaları toplama, diğer karakterlerle etkileşimde bulunma.
    • Güvenli Durum'da: İyileşme, şehirdeki tüccarlarla ticaret yapma.
Bu State Machine Design Pattern, oyunun karmaşıklığını yönetmeye yardımcı olur. Oyunun her anında kahramanın durumu net bir şekilde belirlenir ve oyuncuya hangi eylemleri gerçekleştirebileceği konusunda rehberlik eder. Ayrıca, yeni durumlar eklemek veya mevcut durumları değiştirmek de daha kolaydır, bu da oyunun genişletilebilirliğini artırır.

Kısa bir Unity C# örneği;

C#:
using UnityEngine;

// Durumları temsil eden enumerator ekleyelim.
public enum CharacterState
{
    Idle,
    Walking,
    Running,
    Jumping
}

public class Character : MonoBehaviour
{
    // Şu anki karakter durumunu ayarlayalım.
    private CharacterState currentState;

    void Start()
    {
        // Burada başlangıçta karakter durumunu "Idle" olarak ayarlarız.
        currentState = CharacterState.Idle;
    }

    void Update()
    {
        // Burada durumları sürekli kontrol ederiz ve değişim olduğunda işleriz.
        HandleInput();
        HandleState();
    }

    void HandleInput()
    {
        // Örneğin, kullanıcının girişine bağlı olarak durumu değiştirme
        if (Input.GetKeyDown(KeyCode.Space)) // Space tuşuna bastı.
        {
            ChangeState(CharacterState.Jumping); // Space'e bastıysak zıplıyor olmalıyız?
        }
        else if (Input.GetKey(KeyCode.LeftShift)) // Sol shift tuşuna bastı.
        {
            ChangeState(CharacterState.Running); // Koşuyoruz sanırım!
        }
        else if (Input.GetKey(KeyCode.W)) // W tuşuna bastı.
        {
            ChangeState(CharacterState.Walking); // Sanırım yürüyoruz.
        }
        else
        {
            ChangeState(CharacterState.Idle); // Eylem yok, tuşa veya ekrana dokunulmadıysa "Idle" pozisyonundayızdır.
        }
    }

    void HandleState()
    {
        // Şu anki duruma bağlı olarak bir kontrol mekanizması.
        switch (currentState)
        {
            case CharacterState.Idle:
                Debug.Log("Karakter duruyor.");
                // Idle durumu için gerekli eylemleri yap.
                break;

            case CharacterState.Walking:
                Debug.Log("Karakter yürüyor.");
                // Yürüme durumu için gerekli eylemleri yap.
                break;

            case CharacterState.Running:
                Debug.Log("Karakter koşuyor.");
                // Koşma durumu için gerekli eylemleri yap.
                break;

            case CharacterState.Jumping:
                Debug.Log("Karakter zıplıyor.");
                // Zıplama durumu için gerekli eylemleri yap.
                break;
        }
    }

    void ChangeState(CharacterState newState)
    {
        // Durumu değiştir
        currentState = newState;
    }
}
 
Son düzenleme:
Bu durum kontrolü çoğu yerde vaktinizi kurtaracaktır, çok daha düzenli ve temiz kod yazmanızı sağlayacaktır. İllaki idle, running gibi durumları kullanmak zorunda değilsiniz. GTA tarzı bir prototip yaptığımızı varsayalım. Bu durumları ONFOOT, ONCAR gibi isimlendirerek bazı durumlarda kullanabilirsiniz. Örneğin arabadayken karakterin ateş etme fonksiyonlarının çalışmamasını istiyoruz diyelim. Bunun gibi bir sürü durum var. Hepsini kontrol etmek için ayrı ayrı bool değişkenler oluşturmak yerine bunu ayrı bir kod dosyasında helper olarak kodladığınızda ateş etme kodunuzun başına if(currentState == CharacterState.ONCAR) return; ekleyerek araç içinde oyuncunun ateş etmesini direkt engelleyebilirsiniz.