Unity JsonUtility Kullanımı

Yayınlandı: 03 Nisan 2020 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

Merhabalar,

Bu kısa derste, Unity‘de JsonUtility kullanarak JSON veri okumayı ve kaydetmeyi göreceğiz.

Hızlıca derse başlayalım!

JsonUtility, hem hız hem de GC (garbage collection) anlamında, piyasadaki diğer popüler JSON kütüphanelerinden daha performanslıdır ancak maalesef JsonUtility her veri türünü desteklemez, sadece Unity’nin serialize edebildiği türleri destekler.

Desteklenen Veri Türleri

  • int, float vs. primitive türler
  • Vector3, Quaternion gibi temel Unity struct’ları
  • tek boyutlu array (int[] desteklenirken int[,] ve int[][] desteklenmez)
  • List
  • MonoBehaviour veya ScriptableObject‘ten türeyen sınıflar
  • System.Serializable attribute’üne sahip class ve struct’lar

Desteklenmeyen Veri Türleri

  • Dictionary gibi, Unity’nin serialize edemediği türler; eğer yazdığınız bir script’teki public A obje; değişkeni Inspector’da gözükmüyorsa, Unity A türünü serialize edemiyordur ve haliyle bu tür JsonUtility tarafından desteklenmez
  • Transform, Rigidbody gibi çoğu Unity component’i (bu component’ler MonoBehaviour‘dan değil Component‘ten türerler)
  • Property’ler desteklenmez

Şimdi dilerseniz bir örnek üzerinden, JsonUtility’nin nasıl çalıştığını görelim. Şöyle bir sınıfımız olsun:

// Bu sınıf JsonUtility'i destekler çünkü System.Serializable attribute'üne sahip
[System.Serializable]
public class JSONTest
{
	// JSON'a dahil olur: tek boyutlu array desteklenir ve değişken public
	public int[] array = new int[2] { 11, 13 };
		
	// JSON'a dahil olur: temel Unity struct'ları desteklenir ve
	// değişken private olmasına rağmen SerializeField attribute'üne sahip
	[SerializeField]
	private Vector3 vektor = new Vector3( 1f, 0f, 2f );

	// JSON'a dahil olmaz: primitive türler desteklenir ancak değişken private
	private float sayi = 3.14f;

	// JSON'a dahil olmaz: Dictionary desteklenmez ve değişken private
	public Dictionary<string, int> dictionary = new Dictionary<string, int>();

	// JSON'a dahil olmaz: string desteklenir ve değişken public ancak değişken
	// System.NonSerialized attribute'üne sahip
	[System.NonSerialized]
	public string yazi = "Test";
}

Bir objeyi JSON formatına çevirmek için JsonUtility.ToJson fonksiyonu kullanılır:

void Start()
{
	// Yeni bir JSONTest objesi oluştur ve bunu JSON'a çevir
	string json = JsonUtility.ToJson( new JSONTest() );

	// Oluşturulan JSON'u konsola yazdır
	Debug.Log( json );
}

Konsola şu JSON bastırılır:

{"array":[11,13],"vektor":{"x":1.0,"y":0.0,"z":2.0}}

JsonUtility.ToJson fonksiyonu tercihe bağlı bir 2. parametre alır. Eğer bu parametre true olursa, oluşturulan JSON daha okunabilir bir formatta olur:

// Yeni bir JSONTest objesi oluştur ve bunu JSON'a çevir
string json = JsonUtility.ToJson( new JSONTest(), true );
{
    "array": [
        11,
        13
    ],
    "vektor": {
        "x": 1.0,
        "y": 0.0,
        "z": 2.0
    }
}

NOT: JsonUtility.ToJson fonksiyonu sadece, MonoBehaviour veya ScriptableObject‘ten türeyen sınıflar ile System.Serializable attribute’üne sahip class ve struct’ları JSON’a çevirebilir, bunlar harici objeler için (array, List, int, Vector3 vb.) boş bir JSON döndürür. Bu yüzden diyelim bir array’i JSON’a çevirmek istiyorsanız, içerisinde bu array’i değişken olarak tutan ve System.Serializable attribute’üne sahip bir class veya struct oluşturup onu JsonUtility.ToJson’a parametre olarak vermelisiniz.

JSON veriyi geri objeye çevirmek için, JsonUtility.FromJson fonksiyonu kullanılır:

void Start()
{
	/// JSON OLUŞTURMA
	// Yeni bir JSONTest objesi oluştur ve bunu JSON'a çevir
	string json = JsonUtility.ToJson( new JSONTest() );
		
	/// JSON'U GERİ OBJEYE DÖNÜŞTÜRME
	// Tercih edilen yöntem
	JSONTest obje = JsonUtility.FromJson<JSONTest>( json );
	// Alternatif yöntem
	JSONTest obje2 = (JSONTest) JsonUtility.FromJson( json, typeof( JSONTest ) );
}

Alternatif bir yöntem ise, JsonUtility.FromJson’ın bize yeni bir obje döndürmesi yerine, zaten var olan bir objenin değişkenlerinin üzerine yazmaktır. Bunun için, JsonUtility.FromJsonOverwrite fonksiyonu kullanılır:

void Start()
{
	/// JSON OLUŞTURMA
	// Yeni bir JSONTest objesi oluştur ve bunu JSON'a çevir
	string json = JsonUtility.ToJson( new JSONTest() );

	/// JSON'U GERİ OBJEYE DÖNÜŞTÜRME
	// Yeni bir JSONTest objesi oluştur
	JSONTest obje = new JSONTest();
	// Bu objenin değişkenlerinin değerlerini, JSON'dan okunan değerlerle değiştir
	JsonUtility.FromJsonOverwrite( json, obje );
}

Bu fonksiyon genelde MonoBehaviour veya ScriptableObject’ten türeyen objeler için kullanılır. Böylece sahnedeki bir component’in veya bir ScriptableObject asset’inin değişkenlerinin değerlerini JSON olarak kaydedip daha sonra tekrar yükleyebilirsiniz. Eğer JsonUtility.FromJson kullanmaya çalışırsak, herhangi bir GameObject’in component’i olmayan bir MonoBehaviour oluşturulamayacağı için, fonksiyon hata (exception) verir.

JsonUtility her ne kadar UnityEngine.Object türündeki değişkenleri (Component, ScriptableObject, Material vb.) desteklese de, ben bu türlerde değişkenler kullanmanızı önermem. Sebebini ise bir örnekle göstereyim:

[System.Serializable]
public class JSONTest2
{
	public Transform degisken;
}

void Start()
{
	JSONTest2 obje = new JSONTest2();
	obje.degisken = Camera.main.transform; // Değişkene değer olarak kamerayı ver

	// Objeyi JSON'a çevir
	string json = JsonUtility.ToJson( obje );

	// JSON'u konsola yazdır
	Debug.Log( json );
}

Bu kod, konsola şu JSON’u yazdırır:

{"degisken":{"instanceID":11914}}

Gördüğünüz üzere, Object türündeki değişkenler instanceID‘leri ile JSON’a dahil olurlar. Buradaki sıkıntı ise şu: oyunu her kapayıp açtığınızda, objenin instanceID’si değişir. Örneğin editörü kapatıp açınca bu sefer şu JSON oluşturuldu:

{"degisken":{"instanceID":19068}}

Son olarak da, JSON verinizi dosyada tutmak isterseniz, File.WriteAllText ve File.ReadAllText fonksiyonlarını kullanabilirsiniz (kodun başına using System.IO; yazmayı unutmayın):

private void Start()
{
	// Hedef JSON'ımız, persistentDataPath konumundaki veri.json dosyası
	string jsonKonum = Path.Combine( Application.persistentDataPath, "veri.json" );

	/// JSON OLUŞTURMA
	// Yeni bir JSONTest objesi oluştur ve bunu JSON'a çevir
	string json = JsonUtility.ToJson( new JSONTest() );
	// JSON'u dosyaya yaz
	File.WriteAllText( jsonKonum, json );

	/// JSON'U GERİ OBJEYE DÖNÜŞTÜRME
	// JSON dosyasının var olduğundan emin ol
	if( File.Exists( jsonKonum ) )
	{
		// JSON'u dosyadan oku
		string okunanJson = File.ReadAllText( jsonKonum );
		// Okunan JSON'u objeye çevir
		JSONTest obje = JsonUtility.FromJson<JSONTest>( okunanJson );
	}
}

Bu dersin de sonuna geldik. Bir sonraki dersimizde görüşmek üzere!

yorum
  1. Ahmet dedi ki:

    Tilemap’ı prefab olarak kaydettim. Oyun içinde tilemap’i bloklarla doldururken hem oyundaki tilemap bloklarla doluyor, hem de prefab olan doluyor. Böylece oyun esnasında tilemap üzerinde yaptığımız tüm değişiklikler otomatik olarak tilemap prefabına kaydediliyor. Oyundan çıkıp tekrar girdiğimizde de oyunda tilemap bilgilerini prefabdan çektiği için load edilmiş gibi bir şey oluyor 🙂
    Şimdilik iş görür. Umarım mobilde performans sorununa yol açmaz.

  2. Ahmet dedi ki:

    Terraria tarzı oyun geliştiriyorum. Tilemap’i nasıl kaydedebilirim? Grid üzerindeki blokları Tilemap.Settile (selectedTile, null) yaparak kırıyorum. Tilemap.Settile (selectedTile, blok) yaparak yeni bloklar dizebiliyorum. Minecraft’ın 2D’si gibi yani. Fakat bu koyduğum ve kırdığım blokları nasıl kaydedebilirim?

    • yasirkula dedi ki:

      Oyun alanı 200×200 ise, 200×200’lük bir int array oluşturup blokların tiplerini (hava=0, su=1, toprak=2 vb.) bu array’e değer olarak verebilir ve ardından array’i kaydedebilirsiniz. JsonUtility sadece 1 boyutlu array destekliyor (int[]), o yüzden 2 boyutlu array (int[][] veya int[,]) kullanamazsınız. 2 boyutlu oyun alanını 1 boyutlu array’e koymak için uygulanan klasik yöntem, array’e satırları sırayla koymaktır. Yani array’in ilk 200 elemanı oyun alanının en alttaki satırını, sonraki 200 elemanı onun bir üstündeki satırı depolar.

      JsonUtility yerine BinaryFormatter kullanarak 2 boyutlu array’leri de kaydedebilirsiniz ve kaydetme işlemi daha hızlı olur, ama BinaryFormatter’ın kullanımına internetten bakmanız lazım çünkü benim dersim yok.

      • Ahmet dedi ki:

        Binary ile denedim fakat tilemap’i kaydetmiyor. Peki şöyle yapsam. Tilemap’i prefab olarak kaydetsem. Yani her oyunu save yaptığımda üzerindeki değişiklik yaptığım Tilemap’i önceki prefabın üzerine kaydetsem nasıl olur? Ve bunu nasıl yapabilirim?

      • yasirkula dedi ki:

        Unity oyun esnasında prefab oluşturmayı desteklemiyor, elinizdeki veriyi GameObject, Transform, Tilemap vb. Unity sınıflarından bağımsız bir formata (benim verdiğim örnekte int[]) döküp onu kaydetmek zorundasınız.

      • Ahmet dedi ki:

        Maalesef hiçbir şey anlamadım. Bana basitçe anlatabilir misiniz? Yeni bir c# dosyası oluşturdum. İsmini JSONTest yapıp böyle yaptım:

        [System.Serializable]
        public class JSONTest
        {
        public int[] array = new int[2] {200, 200};
        }

        Daha sonra da yeni bir c# dosyası daha oluşturup ismini JSONSave yapıp içindekileri böyle yaptım:

        void Start()
        {
        string json = JsonUtility.ToJson(new JSONTest());
        JSONTest obje = JsonUtility.FromJson(json);
        Debug.Log(json);
        }

        Buradan sonra ne yapacağım? Yapmak istediğim tek şey tilemap’in içindeki toprak bloklarını oyundan çıkarken saveleyip tekrar oyuna girdiğimizde load etmek.

      • yasirkula dedi ki:

        Elimden maalesef bu kadar geliyor, daha basit bir şekilde anlatabileceğimi sanmıyorum :/

Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google fotoğrafı

Google hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.