Apr 132012
 

In part 1 of “Saving and Loading” (Megabite #24), I showed how PlayerPrefs and PlayerPrefsX could be utilized to save things quite easily into a Unity-based game.  In this edition, I will show how to create a save file on a hard drive – this is more complex, and also more versatile.

Again, there is no need to reinvent the wheel if good code exists elsewhere, so I’ll share this gem I found here:

The Serializer works like this:

You create a holder class for everything you want to save and then you mark it with the Serializable attribute:

[Serializable ()] public class SaveData : ISerializable { public bool foundGem1 = false; public float score = 42; public int levelReached = 3; public SaveData () { } } 

Then you have to define functions for how the class should be loaded and how it should be saved, these should be put inside the class.

 public SaveData (SerializationInfo info, StreamingContext ctxt) { //Get the values from info and assign them to the appropriate properties foundGem1 = (bool)info.GetValue("foundGem1", typeof(bool)); score = (float)info.GetValue("score", typeof(float)); levelReached = (int)info.GetValue("levelReached", typeof(int)); } //Serialization function. public void GetObjectData (SerializationInfo info, StreamingContext ctxt) { info.AddValue("foundGem1", (foundGem1)); info.AddValue("score", score); info.AddValue("levelReached", levelReached); } 

The only thing left now is the save and load functions in your script.

public void Save () { SaveData data = new SaveData (); Stream stream = File.Open("MySavedGame.game", FileMode.Create); BinaryFormatter bformatter = new BinaryFormatter(); bformatter.Binder = new VersionDeserializationBinder(); Debug.Log ("Writing Information"); bformatter.Serialize(stream, data); stream.Close(); } public void Load () { SaveData data = new SaveData (); Stream stream = File.Open("MySavedGame.gamed", FileMode.Open); BinaryFormatter bformatter = new BinaryFormatter(); bformatter.Binder = new VersionDeserializationBinder(); Debug.Log ("Reading Data"); data = (SaveData)bformatter.Deserialize(stream); stream.Close(); } 

Oh, just one thing more, you need to define a VersionDeserializationBinder class, otherwise you can’t load the game when you open it later, something about Unity generates a new key of some sort for the binary formatter, search for “Serialization” on the forum and you can find a post about that.

public sealed class VersionDeserializationBinder : SerializationBinder { public override Type BindToType( string assemblyName, string typeName ) { if ( !string.IsNullOrEmpty( assemblyName ) && !string.IsNullOrEmpty( typeName ) ) { Type typeToDeserialize = null; assemblyName = Assembly.GetExecutingAssembly().FullName; // The following line of code returns the type. typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) ); return typeToDeserialize; } return null; } } 

You will need to use these namespaces to get it working:

using System.Text; using http://System.IO; using System.Runtime.Serialization.Formatters.Binary; using System; using System.Runtime.Serialization; using System.Reflection; 

Done, at last :D

To utilize this code, we’re going to use C# instead of Javascript.  You will want to first create a C# file and name it “SaveData”.  The order of the code above is a bit difficult to read if one has no C# experience, so here it is in complete form:

using UnityEngine;
using System.Collections;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System;
using System.Runtime.Serialization;
using System.Reflection;
[Serializable ()]
public class SaveData : ISerializable {
 public bool foundGem1 = false;
 public float score = 42;
 public int levelReached = 3;
public SaveData () {
 }
 
 public SaveData (SerializationInfo info, StreamingContext ctxt)
 {
 //Get the values from info and assign them to the appropriate properties
 foundGem1 = (bool)info.GetValue("foundGem1", typeof(bool));
 score = (float)info.GetValue("score", typeof(float));
levelReached = (int)info.GetValue("levelReached", typeof(int));
 }
//Serialization function.
public void GetObjectData (SerializationInfo info, StreamingContext ctxt)
 {
info.AddValue("foundGem1", (foundGem1));
 info.AddValue("score", score);
 info.AddValue("levelReached", levelReached);
 }
 
 public void Save () {
SaveData data = new SaveData ();
Stream stream = File.Open("MySavedGame.game", FileMode.Create);
 BinaryFormatter bformatter = new BinaryFormatter();
 bformatter.Binder = new VersionDeserializationBinder(); 
 Debug.Log ("Writing Information");
 bformatter.Serialize(stream, data);
 stream.Close();
}
public void Load () {
SaveData data = new SaveData ();
 Stream stream = File.Open("MySavedGame.gamed", FileMode.Open);
 BinaryFormatter bformatter = new BinaryFormatter();
 bformatter.Binder = new VersionDeserializationBinder(); 
 Debug.Log ("Reading Data");
 data = (SaveData)bformatter.Deserialize(stream);
 stream.Close();
}
 
}
public sealed class VersionDeserializationBinder : SerializationBinder 
{ 
 public override Type BindToType( string assemblyName, string typeName )
 { 
 if ( !string.IsNullOrEmpty( assemblyName ) && !string.IsNullOrEmpty( typeName ) ) 
 { 
 Type typeToDeserialize = null;
assemblyName = Assembly.GetExecutingAssembly().FullName;
// The following line of code returns the type. 
 typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
return typeToDeserialize; 
 }
return null; 
 } 
}

The way it is set up, this code will create a file named MySavedGame.game, and inside it will save the variables “foundGem1″, “score”, and “levelReached”.  Once you have this script saved, you simply need to implement the functions.  The easiest way to do this is with another C# file.

If you are testing this script on a new project, you can simply create a gameObject of any sort – I’m partial to cubes – and attach a new C# file to it.  I named mine “Test”, and here it is:

using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
void Start () {
 SaveData save = new SaveData();
 save.foundGem1 = true;
 save.levelReached = 7;
 save.score = 4000;
 save.Save();
 }
}

As you can see, all it does is set the variables to be saved, and then it calls the Save() function within the SaveLoad file.  In a working title, you’re always going to need to decide the things you want saved, and build a function around that premise.   In the example above, save.Load() should actually read the file and the variables can just as easily be pulled out of the “save” object.

As you can see, it’s quite a bit more complex, but very much worth it to anyone who has plans to expand into multiple save files, more complicated games, or simply a more traditional setup.

 Leave a Reply

Connect with Facebook