Serialization in Unity

Md Maruf Howlader - Sep 8 - - Dev Community

In the dynamic world of game development, managing data efficiently is crucial. One of the most powerful tools for achieving this in Unity is its serialization system. It is an essential component for anyone working in Unity. Serialization in Unity is a powerful yet often overlooked feature that manages how data is saved, loaded, and transferred within a game. In this write-up, I aim to simplify and explain the core concepts of Unity’s serialization system to help you better understand its importance in game development.

SerializeField

Firstly, We use SerializeField with private fields to make them visible in the Unity Inspector. Public fields are automatically serialized, but SerializeField helps keep fields private while still allowing developers to modify values in the Inspector.

[SerializeField] // Makes the private field visible in the Inspector
private int health = 100;
Enter fullscreen mode Exit fullscreen mode

Image description

Serializable Custom Class

We commonly use System.Serializable on a class or object to make it visible in the Inspector and prepare it for saving/loading to/from storage or databases using formats like JSON, YAML, or other formats.

[System.Serializable]
public class Weapon
{
    public string name;
    public int damage;
}

public Weapon sword;

void Start()
{
    Debug.Log("Weapon: " + sword.name + ", Damage: " + sword.damage);
}
Enter fullscreen mode Exit fullscreen mode

Image description

Network Serialization

When transmitting data over a network, we often need to serialize data using NetworkMessage or UNet to ensure the data can be sent properly.

public class PlayerPositionMessage : MessageBase
{
    public Vector3 position;

    public override void Deserialize(NetworkReader reader)
    {
        position = reader.ReadVector3();
    }

    public override void Serialize(NetworkWriter writer)
    {
        writer.Write(position);
    }
}
Enter fullscreen mode Exit fullscreen mode

But Unity serialization does much more

GameObject Instantiation

When you instantiate a GameObject in Unity, it works through serialization. Unity takes the original prefab or object, serializes its state, and then creates a new instance by deserializing that data, effectively copying the properties and values from the original.

public GameObject prefab;

void Start()
{
    // Instantiate a new object based on the serialized state of the prefab
    GameObject newObject = Instantiate(prefab);
    Debug.Log("Instantiated: " + newObject.name);
}
Enter fullscreen mode Exit fullscreen mode

Prefabs are saved as serialized objects in Unity. This means that all the components and properties of the prefab are stored in a serialized form. When you instantiate a prefab, Unity deserializes that data and creates a new instance with all the same settings and references.

Scene Serialization

Unity scenes are serialized objects. Every object in the scene, including its components, position, and properties, is saved in a serialized format. When you load a scene, Unity deserializes the scene file, reconstructing all objects to their previous states.

public GameObject exampleObject;

void Start()
{
    // Move object and save the state in the scene
    exampleObject.transform.position = new Vector3(0, 10, 0);

    // Serialized position will be restored on reload
    Debug.Log("Object moved, and its position is serialized.");
}
Enter fullscreen mode Exit fullscreen mode

ScriptableObjects

ScriptableObjects are powerful because they can hold serialized data outside of scenes. This means you can store configurations, game data, or even level information in them, and the data persists between play sessions.

[CreateAssetMenu(fileName = "GameSettings", menuName = "Game Settings")]
public class GameSettings : ScriptableObject
{
    public float volume;
    public int difficulty;
}

public GameSettings gameSettings;

void Start()
{
    Debug.Log("Volume: " + gameSettings.volume + ", Difficulty: " + gameSettings.difficulty);
}
Enter fullscreen mode Exit fullscreen mode

Image description

Complex Data Structures

Unity's internal data structures, like Renderer settings, use serialization to store configurations between sessions or in assets. Other examples include Materials, Animators, and scene objects, which also rely on serialization to store their data.

[SerializeField]
private Renderer playerRenderer;

void Start()
{
    playerRenderer.material.color = Color.red;
}
Enter fullscreen mode Exit fullscreen mode

Image description

Metadata

Each Unity asset (e.g., Prefabs, Materials) has a corresponding .meta file that stores metadata, including file GUIDs that help maintain asset references. Serialization keeps these relationships intact when editing assets.

[CreateAssetMenu(fileName = "NewWeapon", menuName = "Weapon")]
public class WeaponScriptableObject : ScriptableObject
{
    public string weaponName;
    public int powerLevel;
}
Enter fullscreen mode Exit fullscreen mode

Image description

Mapping references

Mapping references within Prefabs, ScriptableObjects, or even Presets is part of Unity's serialization system. When you serialize an object, Unity ensures these references are stored and loaded correctly.

Undo Functionality

When you press undo in Unity, it uses serialization to restore the previous state of objects, allowing changes to be reversed.

Let's Learn the Basics of Serialization

What is Serialization?

Serialization is the process of converting an object's state into a format (binary, JSON, YAML, etc.) that can be stored, transmitted, or reconstructed later. It’s key for saving data, sending data over networks, and managing Unity assets. Think of it as organizing data in a linear sequence, one item after another.

Deserialization is the reverse of serialization, converting stored or transmitted data back into a usable object or data structure.

Image description

How does it works!

Serialization System is a robust, optimized, and backward-compatible system, written in c++ for better performance.
Through Unity's keynote, I learned that Unity designed a Serialization Backend, which uses a common method called Transfer. This method is responsible for converting basic data types into a well-organized format. A component known as SerializeTraits distributes the requested data to the appropriate Transfer method based on the data type. This system efficiently handles different data types to ensure proper serialization and deserialization, allowing for smooth data saving, loading, and transmission processes in Unity.

Image description

Supported Formats

  • Binary: Efficient and space-saving. Used internally by Unity for asset files.
  • YAML: More human-readable, often used in scenes and prefabs.
  • JSON: Commonly used for network communication and saving files due to its simplicity.

JSON Serialization Example

Serialize and deserialize objects using JSON.

[System.Serializable]
public class PlayerData
{
    public string playerName;
    public int score;
}

public PlayerData playerData = new PlayerData();

void Start()
{
    // Serialize to JSON
    string jsonData = JsonUtility.ToJson(playerData);
    Debug.Log("Serialized Data: " + jsonData);

    // Deserialize from JSON
    string jsonString = "{\"playerName\":\"John\",\"score\":100}";
    PlayerData loadedPlayerData = JsonUtility.FromJson<PlayerData>(jsonString);
    Debug.Log("Deserialized Data: " + loadedPlayerData.playerName + ", Score: " + loadedPlayerData.score);
}
Enter fullscreen mode Exit fullscreen mode

Saving and Loading with Unity’s Serialization

Saving and loading game data using Unity’s serialization (via JSON).

[System.Serializable]
public class GameData
{
    public int level;
    public int lives;
}

public GameData data = new GameData();

void Start()
{
    string path = Application.persistentDataPath + "/savefile.json";

    // Serialize to JSON and save
    string json = JsonUtility.ToJson(data);
    File.WriteAllText(path, json);
    Debug.Log("Game saved!");

    // Load and deserialize
    if (File.Exists(path))
    {
        string loadedJson = File.ReadAllText(path);
        GameData loadedData = JsonUtility.FromJson<GameData>(loadedJson);
        Debug.Log("Level: " + loadedData.level + ", Lives: " + loadedData.lives);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeway

  • Fields must be public or marked with [SerializeField].
  • Static, const, and readonly fields are not serialized.
  • Unity supports serialization of primitive types, enums, built-in types, custom structs, and custom classes.
  • You can also serialize arrays and lists of these types.
// 1. Public field (automatically serialized)
public int publicField = 10;

// 2. Private field with SerializeField (serialized)
[SerializeField]
private float privateField = 3.5f;

// 3. Static field (NOT serialized)
public static int staticField = 5; // Won't be saved

// 4. Const field (NOT serialized)
public const int constField = 100; // Not serialized

// 5. Readonly field (NOT serialized)
public readonly string readOnlyField = "ReadOnly"; // Not serialized

// 6. Primitive data types
public int intField = 25;
public bool boolField = true;

// 7. Enum type (serialized if 32 bits or smaller)
public ExampleEnum enumField = ExampleEnum.Option1;

// 8. Unity built-in types
public Vector3 position = new Vector3(1, 2, 3);
public Color color = Color.red;

// 9. Serializable struct
[Serializable]
public struct CustomStruct
{
    public int value;
    public string name;
}
public CustomStruct customStruct;

// 10. UnityEngine.Object reference
public GameObject objectReference;

// 11. Serializable custom class
[Serializable]
public class CustomClass
{
    public float classValue;
    public string className;
}
public CustomClass customClass;

// 12. Array of serializable types
public int[] intArray = { 1, 2, 3 };

// 13. List of serializable types
public List<string> stringList = new List<string> { "Item1", "Item2" };

// Enum for demonstration
public enum ExampleEnum
{
    Option1,
    Option2
}

Enter fullscreen mode Exit fullscreen mode

Unsupported Types

  • Unity's built-in serialization system doesn't support certain types, like dictionaries, polymorphic objects, or properties/methods, multilevel types (multidimensional arrays, jagged arrays, dictionaries, and nested container types). Avoid nested, recursive structures where you reference other classes.

Unity doesn’t serialize dictionaries by default.


public Dictionary<string, int> scores = new Dictionary<string, int>();

void Start()
{
    scores.Add("Player1", 100);
    scores.Add("Player2", 200);

    // Dictionary won't be serialized automatically by Unity.
}

Enter fullscreen mode Exit fullscreen mode

Polymorphism Limitation Example
Unity’s built-in serialization doesn’t support polymorphism directly.


[System.Serializable]
public class BaseClass
{
    public string baseProperty = "Base Property";
}

[System.Serializable]
public class DerivedClass : BaseClass
{
    public string derivedProperty = "Derived Property";
}

public BaseClass myObject;

void Start()
{
    myObject = new DerivedClass();
    Debug.Log("Base property: " + myObject.baseProperty);
    // Unity won't serialize derivedProperty automatically.
}

Enter fullscreen mode Exit fullscreen mode

Solutions for Unsupported Types:

The ISerializationCallbackReceiver interface in Unity provides callbacks to handle custom serialization and deserialization for data types that Unity's built-in serializer cannot handle. This interface includes two methods: OnBeforeSerialize() and OnAfterDeserialize(), which allow developers to manually process fields before serialization and after deserialization.


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

[Serializable]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
    [SerializeField] private List<TKey> keys = new List<TKey>();
    [SerializeField] private List<TValue> values = new List<TValue>();

    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();

        foreach (KeyValuePair<TKey, TValue> pair in this)
        {
            keys.Add(pair.Key);
            values.Add(pair.Value);
        }
    }
    public void OnAfterDeserialize()
    {
        this.Clear();

        for (int i = 0; i < keys.Count; i++)
        {
            this[keys[i]] = values[i];
        }
    }

}

Enter fullscreen mode Exit fullscreen mode

using UnityEngine;
public class SerializationCallbackScript : MonoBehaviour
{
    public SerializableDictionary<int, string> SerializableDictionary;
    private void Awake()
    {
        SerializableDictionary.Add(1, "One");
        SerializableDictionary.Add(2, "Two");
        SerializableDictionary.Add(3, "Three");
    }
    public void Start()
    {
        string jsonData = JsonUtility.ToJson(SerializableDictionary);
        Debug.Log("Serialized Data: " + jsonData);
    }
}

Enter fullscreen mode Exit fullscreen mode

Image description

Thank you for reading this blog, I’ve shared key insights into Unity’s serialization system to help you better manage data and assets in your projects. I hope it provides a clearer understanding and benefits your development process.

If anything wasn’t clear or you have suggestions for improvement, feel free to leave a comment—I’m happy to respond promptly and discuss how we can make things even better!

. .