Blog

Simple State Saving

Saving the state of a GameObject(read MonoBehavior) via traditional serialization is not possible so I whipped up a simple approach that allows easy saving and loading of data contained in a class for simple state storage and retrieval via PlayerPrefs.

First, create a super-simple, serializable class that you can use as your data container in your GameObject:

[System.Serializable]
public class HeroStats
{
	public float health;
	public float magic;
	public float money;
	public bool talkedToWizard;
	public bool defeatedRedDragon;
	public bool hasMagicKey;
}

Next add an instance of this class to your GameObject as a replacement for anything specific to runtime operation/progress. I typically make this field public so I can monitor and observe things easier in the inspector and load and save to this field as needed. Here’s an example that leverages OnEnable and OnDisable to automatically load and save the state of a GameObject using the values in the HeroStats class from above:

using UnityEngine;
using System.Collections;

public class Hero : MonoBehaviour 
{
	public HeroStats stats;
	
	void OnEnable()
	{
		stats = StateStorage.LoadData("MainHeroStats");	
	}
	
	void OnDisable()
	{
		Save();
	}

	void OnApplicationPause()
	{
		Save();
	}

	void OnApplicationQuit()
	{
		Save();
	}

	void Save()
	{
		StateStorage.SaveData("MainHeroStats", stats);
	}
}

The string values required in the LoadData and SaveData methods are merely references to the PlayerPrefs key that your data will be saved to. I considered making this automated but if multiple objects shared a stats class you would have problems (from this example, what if we had multiple heroes?). I also included a simple wrapper for PlayerPrefs’ DeleteKey method to unify this interface called ClearData. I don’t think you can use generics with UnityScript (which is required so proper serialization and deserialization of your stat class can occur) so I think this approach is strictly for the C# crowd (they are cooler anyway).

If you ever encounter an error: “InvalidOperationException: < CLASSNAME xmlns=” > was not expected…” you renamed the name of the class you were saving into PlayerPrefs. A simple save of data to the same key with the new class name in PlayerPrefs will fix everything.

To save all data to disk to ensure a user’s progress is never lost after a crash there is a CommitData method that can be called. Please note this is a slightly expensive operation so perhaps use it at key points or during transitions.

Download

8 Comments

  1. March 21, 2012

    I’ve looked through the class and I must say, very nice use of generics. Well done!

    However you might want to be careful here. The SaveData method you have will not actually write anything to disk. Unity will not write your saved state to disk until Application.Quit is called. If your game crashes somewhere along the line your state will not get saved at all. You may want to change the name of the SaveData method to SetData and add a new SaveData method that calls into PlayerPrefs.Save(), that way users of the class can be sure that the data is going to be saved. PlayerPrefs.Save can cause a slight drop in frame rate, especially if you are writing a complex XML string to disk so you probably only want to call it during check points or something similar.

    Thats just my 2 cents.
    Anyway thanks for your simple helpful solution.

    • March 21, 2012

      Thanks for the support and kind words.

      I’ve yet to encounter that functionality in PlayerPrefs though. Every project I’ve worked on (desktop and mobile) the PlayerPrefs seem to save immediately. Maybe I’ve missed this?

    • March 21, 2012

      Sorry, re-read what you wrote. Ok, I’ll think on that some and, if nothing else, I’ll just get that method in there for users to have as needed.

    • DenninDalke
      April 23, 2012

      Hey Pixelplacement, what about the PlayerPrefs.Save() method that Jacob told? Does your code persists data only at runtime? Did you think that’s needed?

    • April 23, 2012

      Jacob’s point only matters if your program crashes. It’s a valid point but I rarely have applications crash on me so I haven’t spent any more time on this. That said, this will save data perfectly fine and does not apply to run-time only.

  2. Vinicius
    October 26, 2012

    thanks a lot for this!

    I use javascript instead of C#. The only difference is LoadData that must be called like:

    stats = StateStorage.LoadData.( “MainHeroStats” );

    thanks again!

  3. Vinicius
    October 26, 2012

    hanks a lot for this!
    I use javascript instead of C#. The only difference is LoadData that must be called like:
    stats = StateStorage.LoadData.<HeroStats&lg;( “MainHeroStats” );

    thanks again!

  4. Vinicius
    October 26, 2012

    thanks a lot for this!
    I use javascript instead of C#. The only difference is LoadData that must be called like:
    stats = StateStorage.LoadData.( “MainHeroStats” );

    [sorry for the previous comments, the were removed]

    thanks again!