Unity3D: save game basics

So, you finally built a game on Unity3D that has more than one scene. Congrats. Now you got bunch of user and game generated information you need to pass from one level to another or restore on scene reload. The simplest way to achieve that is XML object serialization/deserialization and saving/loading XML files. Today we gonna do just that.

Brace yourselves, code is coming.

First rule of all savegames – figure out what info cannot be recreated. If you trying to just save the whole scene with GameObjects and shit, you’re doing it wrong. Hit points, status effects on your characters, picked locks, empty loot chests – that’s what really crucial, everything else should be recreateable.

So, your generic savegame info class will look something like this:

public class SaveGameInfo {

    public SaveGameInfo() {
    // your constructor doesn't need to do anything
    // but you can initialize some default values here
    // like, fill the inventory
    } 

    public int heroHitpoints;

    // yeah, structs can be serialized
    public struct InventoryItem {
        public int itemId;
        public int quantity;
    }

    // and arrays too
    public InventoryItem[] items;

}

Notice everything is public. That’s because XML serialization takes only public fields on each serialized object, so if you actually work with your SaveGameInfo instance and not just store in there, you can use private fields to conceal temporary data that doesn’t need to be stored.

Almost done here. For proper XML saving/loading we need a non-nullable root attribute, so the XML will always contain the entire structure of your savegame class. Your SaveGameInfo class declaration now should look like this:

[XmlRootAttribute(ElementName="SaveGame", IsNullable=false)]
public class SaveGameInfo {

Onward, to savegame management. You’ll need a MonoBehaviour subclass, say, SaveGameManager.

public class SaveGameManager : MonoBehaviour {
    // lil' bit of singleton magic in here
    // so you can access it with SaveGameManager.Instance
    public static SaveGameManager Instance { get; private set; }

    // initialize our singleton in Awake() like this
    void Awake() {
        if (Instance != null && Instance != this) {
            Destroy(Instance);
        }
        Instance = this;
    }

    // single instance of SaveGameInfo goes in here
    public SaveGameInfo saveGameData = new SaveGameInfo();
}

You can actually skip this part, if you got some GameManager (I know you do), you can put public field for your SaveGameInfo there. Manipulate game data during gameplay or use OnDestroy() in your manager to store all the data – choice is yours.

Now, to the XML serialization. Whatever class manages your save games should have these four methods:

    string UTF8ByteArrayToString(byte[] characters) 
    {      
        UTF8Encoding encoding = new UTF8Encoding(); 
        string constructedString = encoding.GetString(characters); 
        return (constructedString); 
    } 

    byte[] StringToUTF8ByteArray(string pXmlString) 
    { 
        UTF8Encoding encoding = new UTF8Encoding(); 
        byte[] byteArray = encoding.GetBytes(pXmlString); 
        return byteArray; 
    } 

    // Here we serialize our object into XML string
    string SerializeObject(object pObject) 
    { 
        string XmlizedString = null; 
        MemoryStream memoryStream = new MemoryStream(); 
        XmlSerializer xs = new XmlSerializer(typeof(SaveGameInfo)); // mind the typeof
        XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8); 
        xs.Serialize(xmlTextWriter, pObject); 
        memoryStream = (MemoryStream)xmlTextWriter.BaseStream; 
        XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray()); 
        return XmlizedString; 
    } 

    // Here we deserialize it back into its original form 
    object DeserializeObject(string pXmlizedString) 
    { 
        XmlSerializer xs = new XmlSerializer(typeof(SaveGameInfo)); // mind the typeof
        MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString)); 
        XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8); 
        return xs.Deserialize(memoryStream); 
    }

… and some more usings in the declaration:

using System.Xml; 
using System.Xml.Serialization; 
using System.IO; 
using System.Text; 

That’s it, we’re ready to save and load. Add these two methods to your manager:

    // this one takes a serialized object string as an argument
    void CreateXML(string _data) 
    { 
        StreamWriter writer; 
        FileInfo t = new FileInfo(Application.persistentDataPath + "/"+ "SaveGame.xml"); 
        if (!t.Exists) { 
            writer = t.CreateText(); 
        } else { 
            t.Delete(); 
            writer = t.CreateText(); 
        } 
        writer.Write(_data); 
        writer.Close(); 
    } 

    // this one returns loaded XML string or null if no file exists
    string LoadXML() 
    { 
        string filename = Application.persistentDataPath + "/" + "SaveGame.xml";
        FileInfo t = new FileInfo(filename);
        if (!t.Exists) return null;
        StreamReader r = File.OpenText(filename); 
        string _info = r.ReadToEnd(); 
        r.Close(); 
        return _info;
    }

Notice Application.persistentDataPath – it holds a path to where your application stores the data created during runtime. If someone says to you it’s ok to use Application.dataPath, kick ’em in the balls. Application.dataPath points to Assets, which is perfectly writtable under Unity Editor, but on mobiles Assets folder is a part of immutable bundle – best case scenario is you don’t get to save there, worst case – immediate crash.

Now, finally. My player save data persists between runs and different scenes, so I use OnDestroy() in SaveGameManager to save everything and Start() to restore the state.

    // save all the data on scene closing
    void OnDestroy() {
        string xml = SerializeObject (saveGameData);
        CreateXML (xml);
    }

    // load all the data on scene starting
    void Start () {
        string xml = LoadXML ();
        if (xml != null)
            saveGameData = (SaveGameInfo)DeserializeObject (xml);
        else
            saveGameData = new SaveGameInfo();
    }

Easy and efficient. Feel free to copy-paste. I did.

Advertisements

Tags:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: