Unity ile Alien Farmer projesini prototiplerken geliştirmeye devam ettiğim, oyundaki ilerlemeyi kaydetmek için bir kaydetme/yükleme sistemine ihtiyacım vardı ancak hazır bir sistem kullanmak yerine doğrudan projenin yapısına uygun bir sistem geliştirmek istedim. Bu yazıda anlatmaya çalıştığım sistemin kaynak koduna buradan ulaşabilirsiniz.
Önemli Not: Bu sistemin sadece prototipleme aşamasında bir proje için kullanıldığını, tamamen geliştirme amaçlı olduğunu, yayınlanmaya hazır veya daha büyük gereksinimleri karşılayamayabileceğini unutmayınız.
Save sistemi bağımlılık sırası azdan çok’a doğru olmak üzere 3 modülden oluşuyor:
1- SaveFileHandler -> Veriyi bir dosyaya yazma/okuma görevini yapan modül
2- SaveManager -> Oyunda veri okuma/yazma işlemine erişimi sağlayan arayüz
3- ObjectSaveManager -> Obje üstünde okuma/yazma işlemleri için componentlerin ihtiyaç duyduğu identifierları hazırlama, obje instantiate edildiğinde kayıtlı veri varsa okuma
SaveFileHandler sadece Unity’nin JsonUtility’sine ve Newtonsoft’un Json kütüphanesine bağımlı, eğer sadece veriyi yazıp okuyacak bir modül arıyorsanız bu modülü tek başına kullanabilirsiniz
Obje ve component save sistemini editörde hazırlayan ve çalıştıran modül ise ObjectSaveManager. Bu modülün 2 amacını ve çalışma şeklini kısaca açıklayayım.
1- Instance bazında ve component bazında bir identifier sistemi sağlamak
Component idleri instance bazında aynı olması için varsa prefab’e, yoksa objenin üzerine kaydediliyor, böylece componentlerin id’lerinin tüm instancelarda aynı olması ve gereksiz değişikliklerden kaçınılıyor. Component id olarak doğrudan class ismi gibi sabit bir şey kullanılmamasının sebebi ise bir obje içinde aynı componentten birden fazla olabilmesi.
Instance id’sini ise prefab bazında kaydetmiyoruz ki karışıklıklardan kaçınalım. Eğer objenin prefab olmadığı, sahnede bir instance olduğu anlaşılırsa da id otomatik olarak atanıp kaydediliyor. Ayrıca eğer prefab’ten bir tane olacağını varsayarak sabit bir id atamak istiyorsanız permanent id seçeneğini işaretleyerek kendiniz sabit bir identifier girebilirsiniz.


2- Componentlerin kaydetme/yükleme zamanlamasını ayarlamak
Modül DefaultExecutionOrder ile objenin componentleri Awake metodunu çağırmadan önce veriyi ihtiyaç duyan componentlere yüklettirerek hazır hale getiriyor. Bunu tek yerden yapmasaydık birçok componentte zamanlamayı yapmak için birçok önlem almamız gerekecekti.
Tüm componentlerin veriyi kaydetmek için ayrı ayrı dinlemesi ya da oyundaki tüm componentleri gezerek hepsinde kaydetme metodunu ayrıca tetiklemek yerine bu modül SaveManager’ı dinleyerek kaydetme işlemi tetiklendiğinde kendisine bağlı olan bütün componentlere verilerini kaydetmelerini iletiyor.
Kullanım şekli
Sistemi kullanmak için yapmanız gereken basit, veri tutmak istediğiniz componentte ISaveComponent<T> interface’ini kalıtarak gerekli metod ve propertyleri implemente etmeniz yeterli. Kaydedeceğiniz veri tipinin Serializable olmak zorunda olduğunu unutmayın.
Obje üstündeyse en üstteki parenta ObjectSaveManager scriptini ekleyin. Manager scripti interface üzerinden OnValidate‘te gerekli bilgileri kaydedip, bunları runtime’da kullanarak componentte okuma/kaydetme işlemini kendisi tetikliyor.
Instantiate edilen objelerde kullanma
Instantiate edilen prefablerde bu sistemi kullanmak için ben tüm spawnlanan prefablerimi değişmeyen bir index ile tutan IdentifiedSet yapısını ve basit bir prefab veritabanı olarak PrefabDatabase ScriptableObjesini kullanıyorum. Böylece spawnladığım prefab’i idsini kullanarak kaydetmem, oyunun bir sonraki açılışında bu id’den prefabi bularak tekrar spawnlayabilmek için yeterli oluyor.
//objenin instantiate olduğunda Awake metodunu tetiklememesi için
//prefabi deaktif hale getiriyorum
prefab.gameObject.SetActive(false);
var networkObject = Object.Instantiate(prefab, position, rotation, parent);
//objeyi instantiate ettikten hemen sonra prefabi eski haline getiriyorum
prefab.gameObject.SetActive(true);
//objenin kaydedilmiş id'si var ise modüle bu id'yi kaydediyorum
if (networkObject.TryGetComponent<ObjectSaveManager>(out var saveIdentifier))
{
saveIdentifier.SetRuntimeId(saveId);
}
//ardından objeyi aktifleştiriyorum
networkObject.gameObject.SetActive(true);Sistemin çok basit kalması şu anda benim için büyük bir avantaj ancak bu haldeyken sistemin bazı kısıtlamaları ve duruma göre hem dezavantaj hem de avantaj sayılabilecek özelliklere sahip.
Avantajlar
– Nested veri kullanabilme
– List ve collection kullanabilme
– Diğer componentlerden önce çalışarak Awake’ten önce verinin hazır hale gelmesi
– Component bazlı veri kaydedebilme
– Sahnede bulunan objelerin unique identifierları olması
– Herhangi bir string key ile kaydedebilme, component kullanma zorunluluğu yok
Dezavantajlar
– Kaydedilen tipin namespace’i değişirse veri okunamıyor (kritik)
– Eğer obje runtime’da instantiate oluyorsa id’sini ayrı bir yere kaydedip, spawnlanırken bu idyi setlemek gerekiyor (InstantiatedObjects diye bir liste tutup, bu listeyi özel bir id ile kaydedip, spawnlanan objelerin idsine buradan bakarak data okunabilir)
– MonoBehaviour olmayan classlarda kullanmak için statik bir key veya component erişimi gerekiyor
– Kaydedilen veri okunabilir formatta (şu an için kasıtlı, şifrelemek veya boyutunu küçültmek çok kolay)
– Kayıt için json kullanılıyor (büyük projelerde performans sorununa yol açabilir)
Bir yanıt yazın
Yorum yapabilmek için oturum açmalısınız.