Unity Serialization: SerializeField ile Veri Yönetimi

Unity'de SerializeField kullanarak oyun verilerini nasıl etkili bir şekilde yöneteceğinizi ve Inspector'da görünür hale getireceğinizi öğrenin. Kapsülleme ve ileri seviye kullanım ipuçları.

Unity, oyun geliştiricilerin karmaşık projeleri kolayca oluşturmasına olanak tanıyan güçlü bir platformdur. Bu gücün temel taşlarından biri de veri yönetimi ve serialization mekanizmasıdır. Özellikle Unity Inspector’da değişkenleri düzenlemek, oyunun dinamiklerini değiştirmek için vazgeçilmezdir. Ancak, bir C# sınıfında tanımladığınız her değişken otomatik olarak Inspector’da görünmez. İşte tam bu noktada SerializeField devreye girer ve geliştiricilere inanılmaz bir esneklik sunar.

Bu makalede, Unity’deki script serialization kavramını derinlemesine inceleyecek, SerializeField niteliğinin ne işe yaradığını, neden önemli olduğunu ve projelerinizde nasıl etkin bir şekilde kullanabileceğinizi adım adım öğreneceksiniz. Kapsülleme prensiplerini korurken Inspector’da değişkenlerinizi yönetmenin inceliklerini keşfedeceğiz.

Unity’de Serialization Nedir?

Serialization, bir nesnenin veya veri yapısının durumunu, daha sonra saklanabilecek veya iletilebilecek bir formata dönüştürme işlemidir. Bu işlem, oyununuzun kaydedilmesi, sahne dosyalarının (Scenes) ve prefab’lerin (Prefabs) Inspector’da görünür ve düzenlenebilir olması gibi pek çok temel Unity işlevinin arkasındaki itici güçtür. Unity’nin serialization sistemi, sahnelerdeki GameObject’lerin bileşenlerini, ScriptableObject varlıklarını ve diğer birçok veri tipini otomatik olarak kaydeder ve yükler.

Temel olarak, Unity bir script’i serialize ettiğinde, o script’in belirli alanlarını (değişkenlerini) alır ve bu verileri diske kaydeder. Daha sonra, oyun yüklendiğinde veya sahne açıldığında, bu veriler deserialization işlemiyle geri yüklenir ve script’in önceki durumuna geri dönmesi sağlanır. Bu, geliştiricilerin kod yazmadan birçok parametreyi Inspector üzerinden ayarlayabilmesini ve oyun tasarımcılarının da bu değerleri kolayca değiştirebilmesini sağlar.

SerializeField’ın Gücü: Neden Kullanmalıyız?

C# programlama dilinde, bir sınıfın değişkenleri genellikle public (her yerden erişilebilir) veya private (sadece sınıf içinden erişilebilir) olarak tanımlanır. İyi bir programlama pratiği olan kapsülleme (encapsulation), bir sınıfın iç çalışma mekanizmasını dışarıdan gizlemeyi ve verilere kontrollü erişim sağlamayı önerir. Bu nedenle, birçok değişkeni private olarak tanımlamak tercih edilir.

Ancak, private olarak tanımlanan değişkenler, Unity Inspector’da varsayılan olarak görünmez ve düzenlenemez. İşte [SerializeField] niteliği burada devreye girer. Bir private veya protected alana [SerializeField] niteliğini eklediğinizde, bu alan Inspector’da görünür ve düzenlenebilir hale gelir, ancak C# kodu açısından hala private kalır. Bu sayede, kapsülleme prensibini bozmadan değişkenlerinizi Inspector’dan ayarlayabilirsiniz.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private float _moveSpeed = 5f; // Inspector'da görünecek ve düzenlenecek

    private int _playerHealth = 100; // Inspector'da görünmeyecek

    public string playerName = "Kahraman"; // Public olduğu için zaten görünecek

    void Update()
    {
        // Hareket hızı ile ilgili işlemler...
        transform.Translate(Vector3.forward * _moveSpeed * Time.deltaTime);
    }
}

Yukarıdaki örnekte, _moveSpeed değişkeni private olmasına rağmen [SerializeField] sayesinde Inspector’da görünecektir. _playerHealth ise private olduğu ve [SerializeField] niteliği taşımadığı için görünmeyecektir. playerName ise public olduğu için zaten görünecektir. Bu ayrım, kodunuzu daha düzenli ve güvenli hale getirirken, tasarım esnekliğinden ödün vermemenizi sağlar.

SerializeField Nasıl Çalışır?

Unity, script’lerinizdeki [SerializeField] niteliği taşıyan alanları otomatik olarak tespit eder. Normalde C# reflection kullanarak bu tür alanları bulur, ancak Unity’nin kendi serialization sistemi, performansı artırmak için daha optimize edilmiş bir yaklaşım kullanır. Bu sistem, belirli veri tiplerini serialize etmek üzere tasarlanmıştır.

Desteklenen Veri Tipleri:

  • Tüm temel C# tipleri (int, float, bool, string vb.)
  • Vektörler (Vector2, Vector3, Vector4), Quaternion’lar, Renkler (Color)
  • Diziler (Array) ve Listeler (List)
  • Unity Object’lerinden türetilen sınıflar (GameObject, MonoBehaviour, ScriptableObject vb.)
  • [System.Serializable] niteliği ile işaretlenmiş özel sınıflar ve yapılar (Struct)

Desteklenmeyen Veri Tipleri ve Çözümler:

  • Sözlükler (Dictionary) veya Set’ler gibi karmaşık koleksiyonlar doğrudan serialize edilemez. Bunlar için özel serialization mantığı yazmanız veya desteklenen tiplere dönüştürmeniz gerekir.
  • İç içe geçmiş çok boyutlu diziler (örneğin int[,]) doğrudan serialize edilemez.

Karmaşık tipleri serialize etmek için [System.Serializable] niteliğini kullanabilirsiniz. Bu nitelik, Unity’ye custom bir sınıfı veya yapıyı serialize etme talimatı verir:

using UnityEngine;
using System;

[Serializable] // Bu sınıfın serialize edilebilir olduğunu belirtir
public class ItemStats
{
    public string itemName;
    public int itemID;
    public float damageBonus;
}

public class InventoryManager : MonoBehaviour
{
    [SerializeField]
    private ItemStats _currentItemStats; // ItemStats sınıfı Inspector'da görünecek

    [SerializeField]
    private ItemStats[] _inventoryItems; // ItemStats dizisi de görünecek
}

Serialization Derinlikleri ve İleri Seviye Kullanım

NonSerialized ve Transien Alanlar

Bazen bir değişkeni sadece çalışma zamanında kullanmak isteriz ve onun kaydedilmesini veya Inspector’da görünmesini istemeyiz. Bu tür durumlar için [NonSerialized] veya [System.NonSerialized] niteliğini kullanabilirsiniz. Bu nitelikler, Unity’ye ilgili alanı serialization işleminden muaf tutmasını söyler. Özellikle çalışma zamanında hesaplanan veya sadece geçici olan veriler için kullanışlıdır.

using UnityEngine;
using System;

public class GameStats : MonoBehaviour
{
    [SerializeField]
    private int _totalKills = 0;

    [NonSerialized] // Bu alan kaydedilmeyecek veya Inspector'da görünmeyecek
    private float _currentTimePlayed = 0f;

    void Update()
    {
        _currentTimePlayed += Time.deltaTime;
        // Diğer oyun istatistikleri güncellemeleri...
    }

    public void AddKill()
    {
        _totalKills++;
    }
}

Custom Serialization: Daha Fazla Kontrol

Unity’nin varsayılan serialization sistemi çoğu durumda yeterli olsa da, bazen daha fazla kontrol veya özel bir mantık gerekebilir. Bu tür senaryolar için ISerializationCallbackReceiver arayüzünü kullanabilirsiniz. Bu arayüz, OnBeforeSerialize ve OnAfterDeserialize adında iki metot sağlar. Bu metotları uygulayarak, serialization öncesi ve deserialization sonrası özel işlemler yapabilirsiniz. Örneğin, sözlükleri listelere dönüştürüp serialization öncesi kaydedebilir, sonra deserialization sonrası tekrar sözlüklere çevirebilirsiniz.

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

public class MyCustomSerializer : MonoBehaviour, ISerializationCallbackReceiver
{
    [SerializeField] private List _keys = new List();
    [SerializeField] private List _values = new List();

    private Dictionary _myDictionary = new Dictionary();

    public void OnBeforeSerialize()
    {
        _keys.Clear();
        _values.Clear();
        foreach (var pair in _myDictionary)
        {
            _keys.Add(pair.Key);
            _values.Add(pair.Value);
        }
    }

    public void OnAfterDeserialize()
    {
        _myDictionary.Clear();
        for (int i = 0; i < _keys.Count; i++)
        {
            _myDictionary.Add(_keys[i], _values[i]);
        }
    }

    void Start()
    {
        _myDictionary.Add("Level", 1);
        _myDictionary.Add("Score", 100);
        Debug.Log("Dictionary after deserialization: " + _myDictionary["Level"]);
    }
}

Bu örnekte, _myDictionary doğrudan serialize edilemediği için, OnBeforeSerialize metodunda anahtarları ve değerleri iki ayrı listeye dönüştürerek serialize edilebilir hale getiriyoruz. OnAfterDeserialize metodunda ise bu listelerden tekrar sözlüğü oluşturuyoruz.

SerializeField Kullanımında En İyi Uygulamalar ve İpuçları

  • Kapsüllemeyi Koruyun: Mümkün olduğunca private alanlar için [SerializeField] kullanın. Bu, kodunuzu daha modüler ve hataya daha az yatkın hale getirir.
  • İsimlendirme Kuralları: private [SerializeField] alanlarını ayırt etmek için ön ekler kullanın (örn. _fieldName veya m_fieldName). Bu, kodunuzun okunabilirliğini artırır.
  • Gereksiz Serialization'dan Kaçının: Sadece Inspector'da düzenlenmesi gereken veya kaydedilmesi gereken alanları serialize edin. Çalışma zamanında hesaplanan veya geçici olan veriler için [NonSerialized] kullanın.
  • Performans Farkındalığı: Genellikle [SerializeField] kullanımının performansa doğrudan büyük bir etkisi yoktur. Ancak, çok büyük veri yapılarını sık sık serialize edip deserialize etmek, özellikle mobil cihazlarda performans darboğazlarına yol açabilir.
  • Versiyon Kontrolü ile Uyum: Serialize edilmiş veriler, Unity'nin sahne ve prefab dosyalarında ikili (binary) formatta saklanabilir. Bu dosyaları versiyon kontrol sistemlerinde (Git gibi) yönetirken dikkatli olun. Text formatında serialization'ı etkinleştirmek (Edit -> Project Settings -> Editor -> Asset Serialization -> Mode: Force Text) merge çakışmalarını azaltabilir.

Sonuç

Unity'de script serialization ve SerializeField niteliği, oyun geliştirme sürecinin temel taşlarından biridir. Bu mekanizma sayesinde, kodunuzun yapısını koruyarak değişkenlerinizi Unity Inspector'da kolayca yönetebilir, tasarım esnekliğini artırabilir ve oyununuzu daha dinamik hale getirebilirsiniz. SerializeField'ın nasıl çalıştığını ve ileri seviye kullanım tekniklerini anlamak, daha sağlam, sürdürülebilir ve verimli Unity projeleri geliştirmenize yardımcı olacaktır. Unutmayın, doğru serialization pratikleri, hem kodunuzun sağlığı hem de geliştirme sürecinizin akıcılığı için kritik öneme sahiptir.