Bu yazının ilk versiyonunu yaklaşık 8 sene önce yazmışım. Medium, Linkedin, Devto gibi sitelerde yazdığım yazıları tek bir yerde toplamak istedim. Bu sebeple de eski yazılarımın önce bakımını yapıyor, sonra da onları internet siteme ekliyorum. Amacım onları tek bir yerde toplamak. Tabi böyle söyleyince İskenderiye Kütüphanesi görünümü vermiş gibi oldum ama, o kadar iddialı değilim. Dilim döndüğünce paylaşacağım bakımı yapılmış yazıları. Ve elbette yenilerini de…
Eğer yazdığımız modülleri yeni projelerimizde kullanabiliyorsak, doğru yoldayız. Bunu yapmanın en iyi yollarından biri de, yazdığımız modülleri olabildiğince az bağımlılık ile yazmak ve onları birer UPM ya da diğer adıyla custom git dependency haline getirmek. Böylece kütüphanelerimizi package manager yardımıyla git dependency olarak ya da projemizin manifest.json dosyasına elle kolayca ekleyebiliriz.
Bakımını yeni yaptığım bu pakette ilk dikkatimi çeken ve acilen onarmam gereken şey; bu projenin bir UPM paketi olmamasıydı. Hemen onu bir UPM paketine çevirdim. Nasıl çevirdin diyorsanız, çok detaylı bir dokümanı da buraya iliştiriyorum.
LocalDataManager’ın Geçmişteki Halinin Kısa Bir Özeti
Eski versiyonda iki sınıfı ve iki metodu vardı. LB_LocalDataSaver.SaveData<T> bir obje ile dosya adı alıyor, JsonUtility ile JSON’a çeviriyor ve diske yazıyordu. LB_LocalDataLoader.LoadData<T> dosyayı geri okuyup elinize bir T veriyordu. Kısacası çok basit bir süreci generic yapmışız.
O günkü ihtiyacım için fazlasıyla yeterliydi aslında, sürekli aynı kodu her hobi projemde yazmaktansa, basitçe ortaklaştırmıştım. Eskiden bu proje bitti mi dese biri, bitti derdim, yalan yok. Ancak bugün okuduğumda, ne de yavan bir haldeymiş diyorum.
Yeni LocalDataManager Neler İçeriyor Peki?
Kayıt Sırasında Uygulama Kapanırsa
Eski sistemde buna dair bir korumamız yoktu. Kullanıcı mobil cihazda uygulamasını üste kaydırıp kapatırsa, oyun kapanırken yaptığımız bir kayıt işlemi yarım kalabilirdi. Bu pek istediğimiz bir şey değil ve açıkçası bu durum şarjın bitmesi gibi olası durumlarda dahi olabilir. Sonuç olarak sırtımızı yasladığımı yerel dosya kayıt sistemimiz bizi yarı yolda bırakmış olur. Bunun çözümü ise öyle çok uzay mühendisliği değil; yeni sistemde kayıtlar ara bir dosyaya yapılıyor ve ardından da asıl kayıt dosyası ile yer değiştiriliyor. Böylece yarıda kalmış taşıma işlemi sonrası en yakın eski adıma dönebiliyoruz. Peki bu bizi korur mu? Tamamen değil. Ama eskisine göre daha güvenli olur dostlar.
Kayıt Bozuk mu Yoksa Yok mu?
Eski sürümde Load fonksiyonu çok ilkeldi; dosya yoksa boş bir dosya oluştur ve döndür. Yeni sürümde buna biraz dokunduk, artık sistemimiz bize daha fazla geri bildirim veriyor. Ne demek istediğimi, aşağıdaki örneği inceleyerel daha iyi anlatabilirim sanırım. Kayıt okunmaya çalışılıyor ve eğer varsa oyuna devam ediliyor, eğer bozuk bir dosya varsa ve okunamıyorsa kullanıcıya bu sorun bildiriliyor, eğer bunların hiçbiri yoksa, yeni bir oyun başlatılıyor.
PlayerData playerData;
if (LocalData.TryLoad("PlayerData", out playerData))
{
Continue(playerData);
}
else if (LocalData.Exists("PlayerData"))
{
ShowCorruptSaveDialog();
}
else
{
StartNewGame();
}
Dosya Adlarını Kontrol Etsek İyi Olacak
Eski sürümde kayıt dosyası ismi denetlenmiyormuş. Yani kullanıcı “../save” gibi üst dizine çıkmaya yarayan karakterleri sisteme yollayabiliyor. Böyle durumları denetlemezsek, kullanıcıya farkında olarak ya da olmadan sistemi kırabileceği bir kapı vermiş oluyoruz. Hem konu dosya dizinleri olunca Windows, Android v iOS gibi platformlar bazı konularda birbirleri ile anlaşamıyor. Bu sebeple her birinde yasak olan her şeyi yasaklasak ve kontrol etsek, sistemimiz bu konuda korunaklı olacaktı. Yeni sürüm de tam olarak bunu yapıyor.
JsonUtility Bağımlılığı
Ya biri Newtonsoft kullanmak isterse ne olacak? Eski sürüm buna pek aldırmıyordu. Yeni sürümse IDataSerializer interface’ini kullanarak, sistemle istediğimiz bağımlılığı kendi öz irademizle verebilmemizi sağlıyoruz. Demem o ki, LocalDataManager artık bizi yalnızca JsonUtility kullanmaya zorlamıyor, isteyen istediğini kullanabilir, hatta kendi bile yazabilir.
public class NewtonsoftSerializer : IDataSerializer
{
public string Serialize<T>(T value) => JsonConvert.SerializeObject(value);
public T Deserialize<T>(string data) => JsonConvert.DeserializeObject<T>(data);
}
LocalData.Serializer = new NewtonsoftSerializer();
Ya Biri Kaydı Değiştirirse?
Oyunlarda hile yapmayı övmüyorum ama bu örneği vermem lazım. Sadece bir text dosyasını açarak defans gücümü 10000 yaptığım ilk oyun Blade of Darkness… Bu oyunu da çok severim bu arada. 11 yaşımda delmişim bu kuralı, o zaman neden en azından 11 yaşımdaki halimden kayıt dosyamı korumayayım ki? Eski sürüm bu sorunu çözmüyordu, yeni sürümde dosya şifreleme uyguluyoruz kayıt dosyalarımıza.
Bu arada bu çözüm oyunumuzu sadece bizi bazı 11 yaşındaki çocuklardan korur, hepsinden dahi değil. Yani bu büyük bir güvenlik tedbiri değildir. Projenin içinde tutulan bir anahtar ile yollanırsa bu sistem, kolayca kırılabilir. Kısacası bu çözümü kapıya astığımız basit bir kilit olarak düşünmekte fayda var.
Kaydedilen Tipin Şekli Değiştiğinde Ne Olacak?
Bazen deriz ki, kaydettiğimiz dataya bir tane de renk değeri ekleyelim, yahut hali hazırda kaydettiğimiz ama artık işe yaramayan bir değeri silmek isteriz. Hepimiz yaşamışızdır, o data canlıdır ve değişir. Değişimin en büyük ilacı da versiyonlamak ve versiyondan versiyona datayı taşımak yani migration yapmaktır. Biz de yeni sürümde bunu yapmış olduk. Sistem bizi datayı versiyonlayarak kullanmaya itti ve bu güzel alışkanlığımızı migration fonksiyonelitesi aracılığıyla ödüllendirdi.
[Serializable]
public class PlayerData : IVersionedData
{
public int SchemaVersion { get { return 2; } }
public int Id;
public string DisplayName; // sürüm 1'de bu alanın adı Name idi
}
public class RenameNameToDisplayName : IDataMigration
{
public int FromVersion { get { return 1; } }
public int ToVersion { get { return 2; } }
public string Migrate(string data) => data.Replace("\"Name\":", "\"DisplayName\":");
}
LocalData.Migrations.Register<PlayerData>(new RenameNameToDisplayName());
Aklım Hâlâ Yapmadıklarımda
Evet, hiç bir proje bitmez. Önemli olan, doğru kapsamı belirleyip, o kapsamı yerine getirip, canlıya çıkmaktır. Bittinin tanımını yapmadığımız sürece, iş bitiremeyiz. Üzücüdür bu durum ve bu durumu hayatımızdan acilen def etmemiz gerekir.
Bulut kayıt ve senkronizasyon yok. Güvenlik anlamında şifreleme yok, yukarıda da bahsettim, aldığımız önlem küçük bir zorluk çıkarıyor yalnızca. Binary format yok, her şey metin olarak kalıyor şimdilik.
Bu paketin kapsamı, bir dosyayı yerel diske yazmak ve onu geri okumak ile ilgili. Kapsamı böyle belirleyip, sizlere de okuduğunuz için teşekkür edip, artık dinlenmeye gidebilirim. 🙂
Projenin github linkini burada bulabilirsiniz.