Unity Eğitim
Dersler
Forum Sınav Merkezi Premium 💎
Oyun Geliştirme

Unity ScriptableEvent: Gevşek Bağlı Mimari İçin Güçlü Bir Araç

Paylaşan: Unity Eğitim 05 October 2025 9 dakika okuma 53 görüntülenme

Unity oyun geliştirme sürecinde, farklı sistemlerin ve bileşenlerin birbiriyle iletişim kurması kaçınılmazdır. Ancak bu iletişim, bileşenler arasında sıkı bir bağımlılık oluşturduğunda, projenin bakımı zorlaşabilir, yeniden kullanılabilirlik azalabilir ve hata ayıklama karmaşık hale gelebilir. İşte tam bu noktada gevşek bağlı mimari devreye girer ve Unity ScriptableEvent bu mimariyi uygulamanın en zarif yollarından birini sunar.

Bu makalede, Unity ScriptableEvent'in ne olduğunu, neden gevşek bağlı mimari için önemli olduğunu, nasıl uygulanacağını, pratik ipuçlarını, yaygın hataları ve performans etkilerini detaylı bir şekilde inceleyeceğiz. Amacımız, projelerinizde daha esnek, sürdürülebilir ve yönetilebilir bir yapı kurmanız için size yol göstermektir.

Gevşek Bağlı Mimari ve Unity ScriptableEvent

Gevşek Bağlı Mimari Nedir ve Neden Önemlidir?

Gevşek bağlı mimari (loosely coupled architecture), bir yazılım sistemindeki bileşenlerin birbirleri hakkında minimum düzeyde bilgiye sahip olduğu bir tasarım prensibidir. Yani, bir bileşen, başka bir bileşenin iç işleyişini veya varlığını doğrudan bilmek zorunda kalmadan onunla iletişim kurabilir. Bu, genellikle bir olay (event) sistemi aracılığıyla başarılır.

Peki, neden bu kadar önemli? Gevşek bağlı mimari, özellikle büyük ve karmaşık Unity projelerinde birçok avantaj sağlar:

  • Yeniden Kullanılabilirlik: Bileşenler daha bağımsız olduğu için farklı sahnelerde veya projelerde kolayca yeniden kullanılabilir.
  • Bakım Kolaylığı: Bir bileşende yapılan değişiklikler, diğer bileşenleri minimum düzeyde etkiler. Bu, hata ayıklamayı ve yeni özellikler eklemeyi kolaylaştırır.
  • Esneklik: Sistemin bir bölümünü değiştirmek veya genişletmek, diğer bölümlerde büyük revizyonlar gerektirmez.
  • Test Edilebilirlik: Bileşenler izole bir şekilde test edilebilir, bu da birim testlerini kolaylaştırır.

ScriptableObject ve Unity ScriptableEvent İlişkisi

Unity ScriptableEvent'i anlamak için öncelikle ScriptableObject'in ne olduğunu bilmek gerekir. ScriptableObject, Unity'de veri depolamak ve çalışma zamanında yeni örnekler oluşturmak zorunda kalmadan, proje varlıkları (asset) olarak saklanabilen bir sınıftır. Bu, özellikle oyun ayarları, envanter öğeleri veya bu durumda olduğu gibi olay tanımları gibi paylaşılan veriler için idealdir.

Unity ScriptableEvent, temelinde ScriptableObject'ten türetilmiş bir sınıftır. Bu sayede, event'leri Unity editöründe birer asset olarak oluşturabilir ve farklı MonoBehaviour'lar arasında referans olarak atayabiliriz. Bu yaklaşım, bileşenlerin birbirini doğrudan referans almak yerine, ortak bir event asset'i üzerinden iletişim kurmasını sağlar. Bir bileşen bir event'i tetiklerken (publisher), diğer bileşenler bu event'i dinleyerek (listener) tepki verir. Bu publisher-listener modeli, gevşek bağlı mimarinin temelini oluşturur.

Uygulama: Basit Bir Unity ScriptableEvent Sistemi Oluşturma

Şimdi, parametresiz ve parametreli Unity ScriptableEvent sistemlerini nasıl oluşturacağımızı adım adım görelim.

1. Temel Parametresiz ScriptableEvent

Öncelikle, event'in kendisini tanımlayan bir ScriptableObject sınıfına ihtiyacımız var:

using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "NewGameEvent", menuName = "Events/Game Event")]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();

    public void Raise()
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventRaised();
        }
    }

    public void Register(GameEventListener listener)
    {
        if (!listeners.Contains(listener))
        {
            listeners.Add(listener);
        }
    }

    public void Unregister(GameEventListener listener)
    {
        if (listeners.Contains(listener))
        {
            listeners.Remove(listener);
        }
    }
}

Bu GameEvent sınıfı, dinleyicileri (listeners) tutar ve Raise() metodu ile tüm kayıtlı dinleyicileri bilgilendirir. Register() ve Unregister() metotları ile dinleyiciler event'e abone olabilir veya abonelikten çıkabilir.

Şimdi de bu event'i dinleyecek GameEventListener sınıfını oluşturalım:

using UnityEngine;
using UnityEngine.Events;

public class GameEventListener : MonoBehaviour
{
    [SerializeField] private GameEvent gameEvent;
    [SerializeField] private UnityEvent response;

    private void OnEnable()
    {
        gameEvent.Register(this);
    }

    private void OnDisable()
    {
        gameEvent.Unregister(this);
    }

    public void OnEventRaised()
    {
        response.Invoke();
    }
}

GameEventListener, MonoBehaviour'dan türediği için sahnedeki bir GameObject'e eklenebilir. Inspector'da dinlemek istediği GameEvent asset'ini ve event tetiklendiğinde çalışacak UnityEvent'i (response) atayabiliriz. OnEnable() ve OnDisable() metotları, dinleyicinin yaşam döngüsüne uygun olarak event'e kaydolmasını ve kaydını silmesini sağlar.

2. Parametreli ScriptableEvent (Generic Yapı)

Çoğu zaman, event'lerle birlikte veri de iletmek isteriz (örneğin, hasar alan oyuncunun ID'si, yeni bir puan değeri). Bunun için generic bir yapı kullanabiliriz:

using UnityEngine;
using System.Collections.Generic;

public abstract class BaseGameEvent<T> : ScriptableObject
{
    private List<BaseGameEventListener<T>> listeners = new List<BaseGameEventListener<T>>();

    public void Raise(T value)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventRaised(value);
        }
    }

    public void Register(BaseGameEventListener<T> listener)
    {
        if (!listeners.Contains(listener))
        {
            listeners.Add(listener);
        }
    }

    public void Unregister(BaseGameEventListener<T> listener)
    {
        if (listeners.Contains(listener))
        {
            listeners.Remove(listener);
        }
    }
}

// Örnek: Int türünde veri taşıyan bir event
[CreateAssetMenu(fileName = "NewIntEvent", menuName = "Events/Int Event")]
public class IntGameEvent : BaseGameEvent<int> { }

Parametreli dinleyici sınıfı da benzer şekilde generic olacaktır:

using UnityEngine;
using UnityEngine.Events;

public abstract class BaseGameEventListener<T> : MonoBehaviour
{
    [SerializeField] private BaseGameEvent<T> gameEvent;
    [SerializeField] private UnityEvent<T> response;

    private void OnEnable()
    {
        gameEvent.Register(this);
    }

    private void OnDisable()
    {
        gameEvent.Unregister(this);
    }

    public void OnEventRaised(T value)
    {
        response.Invoke(value);
    }
}

// Örnek: Int türündeki event'i dinleyen sınıf
public class IntGameEventListener : BaseGameEventListener<int> { }

Bu yapılarla, örneğin bir düşman öldüğünde skor event'ini tetikleyip puan değerini iletebilir, veya oyuncu hasar aldığında can değerini UI'ya gönderebilirsiniz. Unity ScriptableEvent sayesinde, bu sistemler birbirini doğrudan tanımadan iletişim kurar.

Pratik İpuçları

1. Editör Dostu Hata Ayıklama

Event'lerin ne zaman tetiklendiğini ve hangi dinleyicilerin tepki verdiğini anlamak bazen zor olabilir. GameEvent sınıfına geçici olarak Debug.Log ifadeleri ekleyerek event akışını takip edebilirsiniz. Ayrıca, ScriptableObject asset'lerine açıklayıcı isimler vermek, projenizde event'leri düzenli tutmanıza yardımcı olur.

public void Raise()
{
    Debug.Log($"Event '{name}' tetiklendi.");
    for (int i = listeners.Count - 1; i >= 0; i--)
    {
        listeners[i].OnEventRaised();
    }
}

2. Kapsamı Belirleme ve İsimlendirme Konvansiyonları

Tüm event'leri global yapmak yerine, belirli bir alt sisteme veya sahneye özgü event'ler oluşturmayı düşünün. Örneğin, 'PlayerHealthChangedEvent' veya 'QuestCompletedEvent'. Bu, event'lerinizi daha yönetilebilir hale getirir ve olası çakışmaları önler. İsimlendirme konvansiyonları (örn: Event adının sonuna 'Event' eklemek) kod okunabilirliğini artırır.

3. Farklı Veri Tipleri İçin Özel Event'ler

Yukarıdaki generic örnekte olduğu gibi, farklı veri tipleri için (string, float, özel sınıflarınız vb.) yeni ScriptableObject event'leri türetebilirsiniz. Bu, event verilerini kesin tipli ve güvenli bir şekilde taşımanızı sağlar. Örneğin, Vector3GameEvent veya CharacterDataGameEvent gibi.

Yaygın Hatalar ve Çözümleri

1. Dinleyicileri Kaydı Silmeyi Unutmak (`Unregister`)

Hata: OnEnable() içinde Register() yaparken, OnDisable() içinde Unregister() yapmayı unutmak. Bu, sahneden kaldırılan veya devre dışı bırakılan nesnelerin hala event listesinde kalmasına neden olabilir. Sonuç olarak, event tetiklendiğinde null referans hataları alabilir veya bellek sızıntıları yaşayabilirsiniz.

Çözüm: Her zaman OnEnable() ve OnDisable() çiftini birlikte kullanın. Bu, dinleyicinin yaşam döngüsüne uygun olarak event'e bağlanıp ayrılmasını sağlar.

2. Event'leri Aşırı Kullanmak

Hata: Her küçük etkileşim veya durum değişikliği için bir Unity ScriptableEvent kullanmaya çalışmak. Bu, projenizi event'lerle doldurabilir, takip etmesi zor bir 'event spaghetti' (event spagettisi) oluşturabilir ve karmaşıklığı artırabilir.

Çözüm: Event'leri sadece gerçekten gevşek bağlılığa ihtiyaç duyulan yerlerde, yani doğrudan referanslamanın istenmediği veya mümkün olmadığı durumlarda kullanın. Küçük, lokal etkileşimler için doğrudan metot çağrıları veya UnityEvent'ler yeterli olabilir.

3. Yanlış Kapsamda Event Tanımlama ve Referans Atama

Hata: Global olması gerekmeyen bir event'i global bir ScriptableObject olarak tanımlamak veya Inspector'da GameEventListener bileşenine doğru GameEvent asset'ini atamayı unutmak.

Çözüm: Event'lerinizi mantıksal gruplara ayırın (örneğin, UI event'leri, oyun mekaniği event'leri). Her GameEventListener'ın doğru GameEvent asset'ine referans verdiğinden emin olun. Bu, özellikle büyük projelerde önemlidir.

Performans ve Optimizasyon Notları

Unity ScriptableEvent sistemleri genellikle hafiftir ve performans üzerinde büyük bir olumsuz etki yaratmaz. Ancak bazı noktaları göz önünde bulundurmak faydalı olabilir:

  • Dinleyici Sayısı: Bir event'in çok sayıda dinleyicisi varsa (yüzlerce veya binlerce), Raise() metodu her dinleyiciyi tek tek çağırdığı için küçük bir performans maliyeti olabilir. Çoğu durumda bu fark edilmez ancak çok sık tetiklenen event'ler için akılda tutulmalıdır.
  • GC (Garbage Collection): ScriptableObject tabanlı event'ler, Action delegeleri gibi çalışma zamanında oluşturulan ve daha fazla GC tetikleyebilen yapılara göre genellikle daha kontrollüdür. Dinleyici listeleri önceden ayrılır ve Add/Remove işlemleri dışında ek bellek tahsisi minimaldir.
  • Veri Aktarımı: Event'lerle taşınan veri miktarı ve karmaşıklığı, performans üzerinde küçük bir etkiye sahip olabilir. Büyük veri yapılarını sık sık event'ler aracılığıyla iletmek yerine, sadece gerekli bilgileri göndermeye özen gösterin.

Sonuç

Unity ScriptableEvent, Unity projelerinizde gevşek bağlı mimariyi benimsemek için son derece güçlü ve esnek bir araçtır. Bileşenler arasındaki bağımlılıkları azaltarak kodunuzu daha okunabilir, bakımı daha kolay ve yeniden kullanılabilir hale getirir. Doğru kullanıldığında, karmaşık oyun sistemlerini yönetmek için sağlam bir temel sunar. Bu makaledeki bilgiler ve örneklerle, kendi projelerinizde Unity ScriptableEvent'leri etkin bir şekilde kullanmaya başlayabilir ve daha profesyonel bir oyun geliştirme deneyimi yaşayabilirsiniz.

🧠 Ders Sonu Değerlendirme Testi

Dersi tamamladıktan sonra bilgilerinizi test edin ve ekstra puanlar kazanın.

🔥 +50 XP Ödül
🔒

Sınava Katılmak İçin Giriş Yapın

Bu ders sonu testini çözebilmek, bilginizi test edip **+50 XP** kazanmak ve **Sınav Şampiyonu** rozetinin kilidini açmak için üye girişi yapmalısınız.

Yorumlar (0)

Yorum yazabilmek ve derslere katkıda bulunabilmek için giriş yapmalısınız.

İlk yorumu siz yapın!