Making Save Games With The Odin Serializer

In this guide, we will look at how you could use the Odin Serializer to implement saving and loading for your games.

Keep in mind that this example is deliberately simple, and is only intended to demonstrate the concept.

Let's say we have a Player component. It has a Health field and a list of items for an inventory. The Item class is just simple data for this example.

public class Player : MonoBehaviour
{
	public float Health;
	public List<Item> Inventory;
}

[Serializable]
public class Item
{
	public string ItemId;
	public int Count;
}

This and a world position is the data we would like to include in our save file. So let's start by moving that out into a new PlayerState class. This separates our data from our Unity component. This is very important, because at runtime, Odin cannot save down references to or the contents of actual Unity objects. We must keep our data clean of references to or dependencies on Unity objects - therefore, separating it out into a clean C# data class is advisable.

For this guide we will convert the Health and Inventory fields to properties, that return the value in the new state field.

public class Player : MonoBehaviour
{
	[SerializeField, HideInInspector]
	private PlayerState state = new PlayerState();

    [ShowInInspector]
    public int Health
	{
		get { return this.state.Health; }
		set { this.state.Health = value; }
	}

	[ShowInInspector]
	public List<Item> Inventory
	{
		get { return this.state.Inventory; }
		set { this.state.Inventory = value; }
	}

	// Serializable is unnecessary for Odin, but it allows for Unity to serialize the
	// PlayerState class in the editor.
	[Serializable]
	public class PlayerState
	{
		public Vector3 Position;
		public float Health;
		public List<Item> Inventory;
	}
}

Great! Now let's save it. We'll just grab the PlayerState.Position value from the Transform component, and then we can use Odin to serialize the PlayerState class.

public void SaveState(string filePath)
{
	this.state.Position = this.transform.position;
	byte[] bytes = SerializationUtility.SerializeValue(this.value, DataFormat.Binary);
	File.WriteAllBytes(filePath, bytes);
}

Saving wouldn't be much good without being able to load again. So let us just add that as well. Read the bytes from the save file if it exists, deserialize the PlayerState with Odin, and set the Transform component's position to the Position value that we just loaded.

public void LoadState(string filePath)
{
	if (!File.Exists(filePath)) return; // No state to load
	
	byte[] bytes = File.ReadAllBytes(filePath);
	this.state = SerializationUtility.DeserializeValue<PlayerState>(bytes, DataFormat.Binary);
	this.transform.position = this.state.Position;
}