Giriş: Neden BinaryFormatter?
Oyun geliştirirken, oyuncuların ilerlemesini, envanterlerini veya ayarlarını kalıcı olarak saklamak kritik bir ihtiyaçtır. Unity’de veri kaydetme ve yükleme için birçok yöntem bulunur. Bu yöntemlerden biri de C# dilinin sunduğu güçlü bir araç olan BinaryFormatter‘dır. BinaryFormatter, nesneleri doğrudan ikili (binary) formatta seri hale getirerek (serialize) diske kaydetmenizi ve daha sonra bu veriyi geri yüklemenizi (deserialize) sağlar. Bu makalede, Unity ortamında BinaryFormatter kullanımı temel prensiplerini, pratik ipuçlarını ve dikkat edilmesi gereken önemli noktaları detaylı bir şekilde inceleyeceğiz.
BinaryFormatter, verilerinizi okunabilir metin formatları (JSON, XML gibi) yerine sıkıştırılmış ikili formatta sakladığı için genellikle daha az yer kaplar ve daha hızlı işlem yapabilir. Ancak, güvenlik ve esneklik açısından bazı dezavantajları da bulunmaktadır. Bu rehber, özellikle küçük ve orta ölçekli projelerde veya hassas olmayan verilerin depolanmasında BinaryFormatter kullanımı için size yol gösterecektir.
BinaryFormatter Nedir ve Nasıl Çalışır?
BinaryFormatter, bir nesne grafiğini (yani birbirine bağlı nesnelerden oluşan bir yapıyı) bir veri akışına (stream) dönüştürmek için kullanılan bir .NET sınıfıdır. Bu işleme serileştirme (serialization) denir. Tersine, bir veri akışından nesne grafiğini yeniden oluşturma işlemine ise deserileştirme (deserialization) adı verilir.
BinaryFormatter‘ın çalışabilmesi için kaydetmek istediğiniz tüm sınıfların ve bu sınıfların içindeki tüm alanların (field) [Serializable] niteliği (attribute) ile işaretlenmiş olması gerekir. Aksi takdirde, serileştirme işlemi sırasında hata alırsınız. Bu nitelik, .NET çalışma zamanına bu sınıfın örneklerinin ikili formata dönüştürülebileceğini bildirir.
Temel Kaydetme İşlemi
Bir nesneyi BinaryFormatter kullanarak kaydetmek için aşağıdaki adımları izleriz:
- Kaydedilecek verileri tutan bir sınıf oluşturun ve
[Serializable]ile işaretleyin. - Bir
FileStreamoluşturarak dosya yolunu ve erişim modunu belirtin. BinaryFormatter‘ın bir örneğini oluşturun.BinaryFormatter‘ınSerializemetodunu çağırarak nesneyiFileStream‘e yazın.FileStream‘i kapatın.
İşte basit bir örnek:
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
// Kaydedilecek veri yapısı
[System.Serializable]
public class OyuncuVerisi
{
public string oyuncuAdi;
public int seviye;
public float can;
public Vector3 pozisyon; // Vector3 de Serializable olduğu için doğrudan kullanılabilir
}
public static class KayitSistemi
{
public static void VeriKaydet(OyuncuVerisi veri, string dosyaAdi)
{
// Dosya yolunu belirle (Application.persistentDataPath, platformlar arası uyumludur)
string yol = Application.persistentDataPath + "/" + dosyaAdi + ".dat";
// FileStream kullanarak dosyayı aç veya oluştur
using (FileStream stream = new FileStream(yol, FileMode.Create))
{
// BinaryFormatter örneği oluştur
BinaryFormatter formatter = new BinaryFormatter();
// Veriyi serileştir ve dosyaya yaz
formatter.Serialize(stream, veri);
}
Debug.Log("Veri kaydedildi: " + yol);
}
}
Temel Yükleme İşlemi
Kaydedilmiş bir nesneyi geri yüklemek için de benzer adımlar izlenir:
- Dosyanın var olup olmadığını kontrol edin.
- Bir
FileStreamoluşturarak dosya yolunu ve erişim modunu belirtin. BinaryFormatter‘ın bir örneğini oluşturun.BinaryFormatter‘ınDeserializemetodunu çağırarak veriyiFileStream‘den okuyun ve orijinal türüne dönüştürün (cast).FileStream‘i kapatın.
İşte yükleme örneği:
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public static class KayitSistemi
{
public static OyuncuVerisi VeriYukle(string dosyaAdi)
{
string yol = Application.persistentDataPath + "/" + dosyaAdi + ".dat";
if (File.Exists(yol))
{
using (FileStream stream = new FileStream(yol, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
// Veriyi deserileştir ve orijinal türüne dönüştür
OyuncuVerisi veri = formatter.Deserialize(stream) as OyuncuVerisi;
Debug.Log("Veri yüklendi: " + yol);
return veri;
}
}
else
{
Debug.LogWarning("Kayıt dosyası bulunamadı: " + yol);
return null; // Veya varsayılan bir OyuncuVerisi döndür
}
}
}
Pratik İpuçları ve En İyi Uygulamalar
1. Veri Sınıflarınızı Düzenleme
[Serializable] niteliği, tüm sınıfın serileştirilebilir olmasını sağlar. Ancak bazen bir sınıf içindeki belirli alanların kaydedilmesini istemeyebilirsiniz (örneğin, geçici veriler, sadece çalışma zamanında anlam ifade eden referanslar). Bu durumda, o alanın üzerine [NonSerialized] niteliğini ekleyebilirsiniz. Bu, BinaryFormatter‘ın o alanı yoksaymasını sağlar.
[System.Serializable]
public class OyuncuVerisi
{
public string oyuncuAdi;
public int seviye;
public float can;
[System.NonSerialized]
public GameObject mevcutHedef; // Bu alan kaydedilmeyecek
}
Ayrıca, Dictionary gibi bazı koleksiyon türleri doğrudan BinaryFormatter ile serileştirilemez. Bu tür durumlar için özel serileştirme mantığı (ISerializable arayüzü) uygulamanız veya bunları List gibi serileştirilebilir tiplere dönüştürmeniz gerekebilir.
2. Güvenlik ve Şifreleme
BinaryFormatter ile kaydedilen veriler, doğrudan ikili formatta olsalar da, özel araçlarla okunabilir ve değiştirilebilir. Bu nedenle, hassas kullanıcı bilgileri (şifreler, kişisel veriler) veya oyun içi ekonomi verileri gibi kolayca manipüle edilmemesi gereken veriler için BinaryFormatter tek başına yeterli değildir. Bu tür durumlarda, kaydedilen veriyi bir şifreleme algoritmasından geçirerek ek bir güvenlik katmanı eklemeniz önemlidir. Ancak bu durum, BinaryFormatter kullanımı karmaşıklığını artırır ve genellikle alternatif serileştirme yöntemleri (örneğin, System.Text.Json veya Protocol Buffers) daha iyi bir seçenek olabilir.
Önemli Not: Microsoft, .NET 5 ve sonraki sürümlerden itibaren BinaryFormatter‘ın güvenlik açıkları nedeniyle kullanımını önermemekte ve çoğu durumda kullanımdan kaldırmıştır. Unity’nin eski .NET sürümleriyle uyumluluğu nedeniyle hala kullanılabilse de, yeni projelerde veya hassas veri işleme senaryolarında alternatifleri değerlendirmek daha güvenli bir yaklaşımdır.
3. Veri Yapısında Sürüm Kontrolü
Oyununuz geliştikçe, kaydettiğiniz veri sınıflarınızın yapısı değişebilir (yeni alanlar ekleme, mevcut alanları kaldırma veya yeniden adlandırma). BinaryFormatter, bu tür değişikliklere karşı oldukça hassastır. Eski bir sürümle kaydedilmiş bir veriyi yeni bir sürümle yüklemeye çalıştığınızda hatalarla karşılaşabilirsiniz.
Bu sorunu aşmak için:
- Geriye Dönük Uyumluluk (Backward Compatibility): Yeni alanlar eklerken, bunları
[OptionalField]niteliği ile işaretleyebilirsiniz. Bu, eski kayıtlarda bu alanlar olmadığında hata vermemesini sağlar. - Sürüm Numarası: Kaydettiğiniz veri sınıfına bir sürüm numarası ekleyerek, yükleme sırasında bu numarayı kontrol edip eski sürümler için veri dönüştürme (migration) mantığı uygulayabilirsiniz.
Yaygın Hatalar ve Çözümleri
1. [Serializable] Nitelik Eksikliği
Hata: SerializationException: Type 'YourClass' is not marked as Serializable.
Çözüm: Kaydetmek istediğiniz tüm sınıfları ve bu sınıfların içinde serileştirilebilir olması gereken tüm referans tiplerini [System.Serializable] niteliği ile işaretlemeyi unutmayın.
2. FileStream‘i Kapatmamak
Hata: Dosya kilitlenmeleri veya veri bozulmaları.
Çözüm: FileStream nesnesini her zaman using bloğu içinde kullanın. Bu, akışın işi bittiğinde otomatik olarak kapatılmasını ve kaynakların serbest bırakılmasını sağlar.
3. Tür Uyuşmazlıkları ve Sürüm Farklılıkları
Hata: SerializationException veya beklenmedik değerler.
Çözüm: Yukarıda bahsedildiği gibi, veri yapınızdaki değişiklikleri dikkatle yönetin. [OptionalField] kullanın veya sürüm kontrolü ile veri dönüştürme mantığı ekleyin. Mümkünse, serileştirmeden önce ve sonra verilerinizi test edin.
4. Güvenlik Açıkları (Untrusted Data Deserialization)
Hata: Kötü niyetli kullanıcıların özel olarak hazırlanmış ikili verilerle sisteminize saldırması.
Çözüm: BinaryFormatter‘ı asla güvenilmeyen kaynaklardan (örneğin, ağdan gelen veriler, kullanıcı tarafından değiştirilebilen dosyalar) gelen verileri deserileştirmek için kullanmayın. Bu ciddi güvenlik açıklarına yol açabilir. Eğer böyle bir senaryonuz varsa, JSON, XML veya Protocol Buffers gibi daha güvenli serileştirme formatlarını tercih edin.
Performans ve Optimizasyon Notları
BinaryFormatter, metin tabanlı serileştiricilere göre (JSON, XML) genellikle daha kompakt bir çıktı üretir ve küçük ile orta ölçekli veri setleri için oldukça hızlı olabilir. Ancak, çok büyük ve karmaşık nesne grafikleri üzerinde çalışırken yansıma (reflection) tabanlı doğası nedeniyle performans darboğazları yaşayabilir. Bu tür senaryolarda, özellikle performansa duyarlı uygulamalarda, daha optimize edilmiş serileştirme kütüphaneleri (örneğin, protobuf-net veya Json.NET gibi ikili formatta çalışan JSON serileştiriciler) daha iyi sonuçlar verebilir.
Genel olarak, BinaryFormatter kullanımı basit ve dahili veri kaydetme ihtiyaçları için uygun olabilir. Ancak, veri boyutunuz büyüdükçe veya güvenlik endişeleriniz arttıkça alternatif serileştirme yöntemlerini araştırmanız faydalı olacaktır.
Sonuç
Unity’de BinaryFormatter kullanımı, nesneleri ikili formatta kaydetmek ve yüklemek için basit ve etkili bir yol sunar. Özellikle oyun içi ilerleme veya ayarlar gibi dahili verileri yönetmek için pratik bir çözümdür. Ancak, güvenlik açıkları ve sürüm kontrolü gibi potansiyel zorlukları göz önünde bulundurarak dikkatli kullanılmalıdır. Her zaman olduğu gibi, projenizin özel gereksinimlerini değerlendirerek en uygun serileştirme yöntemini seçmek en doğrusudur. Umarım bu rehber, Unity projelerinizde veri kaydetme süreçlerinizi daha iyi anlamanıza ve uygulamanıza yardımcı olmuştur.



