Oyun geliştirme süreçlerinde, farklı oyun bileşenlerinin birbirleriyle iletişim kurması kaçınılmazdır. Bir karakterin canı azaldığında UI’ın güncellenmesi, bir düşman öldüğünde skorun artırılması veya bir seviye tamamlandığında yeni bir sahneye geçilmesi gibi senaryolar, bu iletişimin temel örnekleridir. Geleneksel yaklaşımlar genellikle doğrudan referanslar veya tekil (singleton) yöneticiler aracılığıyla bu iletişimi sağlar. Ancak bu yöntemler, kod bağımlılığını artırır, yeniden kullanılabilirliği azaltır ve büyük projelerde hata ayıklamayı zorlaştırır.
Giriş: Oyun Geliştirmede Esnekliği Artırın
Modern oyun geliştirme pratiklerinde, bileşenler arasındaki bağımlılığı en aza indiren ve daha esnek bir yapı sunan çözümler aranır. İşte tam da bu noktada, Unity’nin ScriptableObject özelliği tabanlı bir oyun etkinlik (Game Event) sistemi devreye girer. Bu sistem, oyununuzdaki farklı parçaların birbirlerinden haberdar olmadan, ancak belirli olaylara tepki vererek iletişim kurmasını sağlar. Böylece, kodunuz daha temiz, daha sürdürülebilir ve yönetilebilir hale gelir.
Neden Bir Etkinlik Sistemi?
Bir etkinlik sistemi, oyununuzdaki bileşenler arasındaki bağlantıyı gevşetir. Bir nesne bir olay tetiklediğinde, bu olayı kimin dinlediğini veya kaç kişinin dinlediğini bilmesine gerek kalmaz. Benzer şekilde, bir dinleyici de olayı kimin tetiklediğini umursamaz. Bu gevşek bağlılık (decoupling), aşağıdaki avantajları sunar:
- Yeniden Kullanılabilirlik: Aynı etkinliği farklı senaryolarda ve farklı nesnelerle kolayca kullanabilirsiniz.
- Modülerlik: Oyununuzun farklı bölümlerini bağımsız olarak geliştirebilir ve test edebilirsiniz.
- Ölçeklenebilirlik: Yeni özellikler eklemek veya mevcut özellikleri değiştirmek, sistemin diğer bölümlerini etkilemeden daha kolay hale gelir.
- Hata Ayıklama Kolaylığı: Bağımlılıklar azaldığı için hataların kaynağını bulmak daha kolaydır.
ScriptableObject Nedir ve Neden Kullanmalıyız?
Unity’deki ScriptableObject, MonoBehaviour’dan farklı olarak bir sahnedeki GameObject’e atanmayan, ancak proje varlığı olarak kaydedilebilen bir veri kapsayıcısıdır. Genellikle veri depolamak, ayarları yapılandırmak veya paylaşılan mantığı tutmak için kullanılır. Bir ScriptableObject, bir kez oluşturulduğunda projenizin farklı yerlerinden referans alınabilir ve değiştirilebilir. Bu özellikleri, onu bir etkinlik sistemi için ideal bir temel yapar:
- Kalıcı Veri: Çalışma zamanında oluşturulan etkinlik örnekleri, sahne yüklemeleri arasında kaybolmaz.
- Kolay Referans: Editörden sürükle-bırak yöntemiyle kolayca referans verilebilir.
- Merkezi Kontrol: Tüm dinleyiciler ve tetikleyiciler aynı ScriptableObject örneği üzerinden iletişim kurar.
ScriptableObject Tabanlı Oyun Etkinlik Sisteminin Temelleri
Bu sistemi kurmak için iki ana bileşene ihtiyacımız var:
1. GameEvent (ScriptableObject): Bu, tetiklenecek olayın kendisini temsil eder ve dinleyicileri kaydetme/kaldırma ve olayı tetikleme mantığını içerir.
2. GameEventListener (MonoBehaviour): Bu, bir GameEvent’i dinleyen ve olay tetiklendiğinde belirli bir eylemi gerçekleştiren bileşendir.
GameEvent ScriptableObject Oluşturma
Öncelikle, tüm olaylarımızın temelini oluşturacak olan GameEvent sınıfını tanımlayalım. Bu sınıf, dinleyicilerin bir listesini tutacak ve bir olay tetiklendiğinde bu dinleyicileri bilgilendirecektir.
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(menuName = "Game Events/Game Event")]
public class GameEvent : ScriptableObject
{
private List<GameEventListener> listeners = new List<GameEventListener>();
public void Raise()
{
// Dinleyicileri tersten çağırıyoruz ki, bir dinleyici listeden kendini çıkarırsa sorun yaşanmasın.
for (int i = listeners.Count - 1; i >= 0; i--)
{
listeners[i].OnEventRaised();
}
}
public void RegisterListener(GameEventListener listener)
{
if (!listeners.Contains(listener))
{
listeners.Add(listener);
}
}
public void UnregisterListener(GameEventListener listener)
{
listeners.Remove(listener);
}
}
Yukarıdaki kodda:
[CreateAssetMenu]niteliği, Unity editöründe “Assets > Create > Game Events > Game Event” yolundan yeni birGameEventScriptableObject oluşturmanızı sağlar.listenerslistesi, bu olayı dinleyen tümGameEventListener‘ları tutar.Raise()metodu, olayı tetikler ve kayıtlı tüm dinleyicilerinOnEventRaised()metodunu çağırır. Dinleyicileri tersten çağırmak, bir dinleyicinin olayı dinlemeyi bırakması durumunda (örneğin, yok edildiğinde) oluşabilecek hataları önler.RegisterListener()veUnregisterListener()metotları, dinleyicilerin listeye eklenmesini veya listeden çıkarılmasını sağlar.
GameEventListener MonoBehaviour Oluşturma
Şimdi de, oyun nesnelerimize ekleyerek bir GameEvent‘i dinlemesini sağlayacak olan GameEventListener sınıfını oluşturalım. Bu bileşen, bir GameEvent‘e abone olacak ve olay tetiklendiğinde tanımlı bir UnityEvent‘i çağıracaktır.
using UnityEngine;
using UnityEngine.Events;
public class GameEventListener : MonoBehaviour
{
[Tooltip("Dinlenecek GameEvent.")]
public GameEvent Event;
[Tooltip("Etkinlik tetiklendiğinde çağrılacak yanıt.")]
public UnityEvent Response;
private void OnEnable()
{
if (Event != null)
{
Event.RegisterListener(this);
}
}
private void OnDisable()
{
if (Event != null)
{
Event.UnregisterListener(this);
}
}
public void OnEventRaised()
{
Response.Invoke();
}
}
Bu kod bloğunda:
Eventdeğişkeni, dinlenecekGameEventScriptableObject’ini tutar. Bunu Unity editöründen sürükleyip bırakarak atayacağız.Responsedeğişkeni bir UnityEvent‘tir. Bu, olay tetiklendiğinde Unity editöründe doğrudan çağrılacak metotları tanımlamamızı sağlar. Bu sayede kod yazmadan görsel olarak fonksiyonları bağlayabiliriz.OnEnable()metodunda,GameEvent‘e dinleyici olarak kaydediliriz. GameObject aktif olduğunda dinlemeye başlarız.OnDisable()metodunda ise,GameEvent‘ten kaydımızı sileriz. GameObject devre dışı bırakıldığında veya yok edildiğinde artık olayı dinlemeyiz, bu da bellek sızıntılarını önler.OnEventRaised()metodu,GameEventtarafından çağrılır veResponse.Invoke()ile tanımladığımız UnityEvent’i tetikler.
Sistem Nasıl Çalışır? Uygulama Adımları
Sistemin temel bileşenlerini oluşturduğumuza göre, şimdi bunu nasıl kullanacağımıza bakalım.
Bir Etkinlik Nasıl Tetiklenir? (Sender)
Bir olayı tetiklemek isteyen herhangi bir MonoBehaviour sınıfında, ilgili GameEvent ScriptableObject’ine bir referans tutmanız ve uygun zamanda Raise() metodunu çağırmanız yeterlidir.
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private float currentHealth = 100f;
[SerializeField] private GameEvent onPlayerDiedEvent; // Unity editöründen atanacak
public void TakeDamage(float amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
// Oyuncu öldüğünde olayı tetikle
if (onPlayerDiedEvent != null)
{
onPlayerDiedEvent.Raise();
Debug.Log("Oyuncu öldü etkinliği tetiklendi!");
}
Destroy(gameObject);
}
}
}
Bu senaryoda, PlayerHealth bileşeni bir onPlayerDiedEvent ScriptableObject’ine sahiptir. Oyuncunun canı sıfırın altına düştüğünde, bu olay tetiklenir.
Bir Etkinliğe Nasıl Abone Olunur? (Listener)
Bir olayı dinlemek ve ona tepki vermek için, bir GameObject’e GameEventListener bileşenini eklemeniz gerekir. Daha sonra, editörde dinlemek istediğiniz GameEvent‘i Event alanına sürükleyip bırakın ve Response alanında olay tetiklendiğinde çağrılacak metotları tanımlayın.
Örnek olarak, oyuncu öldüğünde UI’da bir “Game Over” panelini etkinleştirmek isteyebilirsiniz:
- Sahnenizde bir UI Canvas oluşturun ve içine bir “Game Over” paneli ekleyin (başlangıçta deaktif olsun).
- Yeni bir boş GameObject oluşturun (örneğin “GameEventsManager”).
- Bu GameObject’e
GameEventListenerbileşenini ekleyin. Eventalanına, daha önce oluşturduğunuzonPlayerDiedEventScriptableObject’ini sürükleyin.Responsealanında+butonuna tıklayın.- Yeni slot’a “Game Over” panelini içeren GameObject’i sürükleyin.
- Fonksiyon açılır menüsünden
GameObject > SetActive (bool)seçeneğini seçin ve argümanıtrueolarak ayarlayın.
Artık oyuncu öldüğünde, PlayerHealth bileşeni onPlayerDiedEvent‘i tetikleyecek, bu da GameEventsManager üzerindeki GameEventListener‘ı bilgilendirecek ve “Game Over” paneliniz aktif hale gelecektir.
ScriptableObject Tabanlı Sisteminin Avantajları
Bu sistemin sağladığı faydalar, oyununuzun karmaşıklığı arttıkça daha da belirginleşir.
Gevşek Bağlılık (Decoupling)
En büyük avantajlardan biri, oyun bileşenleriniz arasında gevşek bağlılık sağlamasıdır. PlayerHealth bileşeni, “Game Over” panelinin veya skor sisteminin varlığından haberdar olmak zorunda değildir. Sadece bir olayı tetikler. Benzer şekilde, “Game Over” paneli de olayı kimin tetiklediğini bilmez, sadece belirli bir olaya tepki verir. Bu, kodunuzu daha modüler ve yönetilebilir hale getirir.
Yeniden Kullanılabilirlik ve Ölçeklenebilirlik
Oluşturduğunuz GameEvent ScriptableObject’leri, projenizin farklı yerlerinde defalarca kullanılabilir. Örneğin, aynı onPlayerDiedEvent‘i hem UI panelini açmak, hem oyun müziğini durdurmak, hem de skor kaydını tutmak için kullanabilirsiniz. Yeni özellikler eklemek istediğinizde, mevcut kod üzerinde değişiklik yapmak yerine yeni bir GameEventListener eklemeniz yeterli olacaktır. Bu, oyununuzu kolayca ölçeklendirmenizi sağlar.
Hata Ayıklama Kolaylığı
Düşük bağımlılık sayesinde, bir hata meydana geldiğinde sorunun kaynağını bulmak daha kolaydır. Belirli bir olayın tetiklenip tetiklenmediğini veya dinleyicilerin doğru şekilde kaydedilip kaydedilmediğini takip etmek, karmaşık referans zincirlerini takip etmekten çok daha basittir. Ayrıca, Unity editöründeki ScriptableObject varlıklarını inceleyerek olay akışını görsel olarak takip edebilirsiniz.
Gelişmiş Kullanım Senaryoları ve Dikkat Edilmesi Gerekenler
Bu temel sistem, çoğu senaryo için yeterli olsa da, daha karmaşık ihtiyaçlar için geliştirilebilir.
Parametreli Etkinlikler
Bazen olayla birlikte bazı verilerin de iletilmesi gerekebilir (örneğin, hasar alan oyuncunun ID’si, toplanan eşyanın türü). Bunu yapmak için, GameEvent ve GameEventListener sınıflarını genel (generic) hale getirebilir veya her parametre türü için ayrı ScriptableObject’ler oluşturabilirsiniz. Örneğin, GameEvent<T> ve GameEventListener<T> gibi yapılar kurarak olayla birlikte veri gönderebilirsiniz.
Editör Entegrasyonu
Daha gelişmiş bir sistemde, olayların Unity editöründe nasıl tetiklendiğini veya dinleyicilerin nasıl davrandığını gösteren özel editör araçları (custom inspectors) geliştirebilirsiniz. Bu, özellikle büyük ekiplerde iş akışını hızlandırabilir ve hata riskini azaltabilir.
Sonuç: Daha Düzenli ve Güçlü Oyunlar İçin
Unity’de ScriptableObject tabanlı bir oyun etkinlik sistemi kurmak, oyun geliştirme sürecinizde size önemli avantajlar sağlayacaktır. Kodunuzu daha düzenli, daha esnek ve daha sürdürülebilir hale getirerek, büyük ölçekli ve karmaşık oyunlar geliştirirken karşılaşılan birçok zorluğun üstesinden gelmenize yardımcı olur. Bu sistem, oyununuzdaki bileşenler arasında gevşek bağlılık sağlayarak, her bir parçanın kendi sorumluluğunu yerine getirmesini ve diğerlerinden bağımsız olarak gelişmesini mümkün kılar. Bu rehberdeki adımları takip ederek, kendi oyun projelerinizde bu güçlü deseni uygulamaya başlayabilir ve daha sağlam bir mimari inşa edebilirsiniz.



