Go to Top

You often want to save data between scenes or between executions of your game. This tutorial shows you how to save data to servers, files and into PlayerPrefs, without being limited to simple primitive values.

Motivation

You should read this article if:

  • You'd like to know how to store persistent data for your game
  • You need to store more complicated things than basic primitive types

Introduction

Storing data in Unity is a bit of a science.  If you want to save entire scenes, player positions etc then the task can be daunting.  I've built Unity Serializerthat makes that process a lot easier and future tutorials in this series will cover using that to create a complete progress saving solution. This tutorial covers some of the other ways of storing data and passing it between versions of the game, for instance across an RPC connection or a server.

The basics

Ok, so the basics.  If you want a piece of data to be saved in Unity your first port of call is probably PlayerPrefs.  PlayerPrefs allows you to store basic primitive values with a simple string key.  It's often a good idea to choose meaningful, namespaced keys in case you start trampling all over your own data later!

//C# & JS

PlayerPrefs.SetInt("Score", currentScore);
PlayerPrefs.SetInt("HighScore1", currentScore);
PlayerPrefs.SetString("HighScore1Name", currentPlayerName);

//Getting values

highScore1 = PlayerPrefs.GetInt("HighScore1");
highScore1Name = PlayerPrefs.GetString("HighScore1Name", "N/A"); //Optional default value

In game values

If you just need to store data in between scenes then the best idea is to create a static class and set values on that:

//JS

public static class PermanentVariables
{
    public var playing : boolean;
    public var playerName : String;
}

//C#

public static class PermanentVariables
{
    public static bool playing;
    public static string playerName = "";
}

Then to access these values it's just:

PermanentVariables.playerName = "whydoidoit";

The downside with classes like this is that you can't set the variables using the Inspector - so if you want to use a combination of Inspector and code access you want to consider a DontDestroyOnLoadobject that is present in each scene or your starting  scene.   You could do that like this:

public class SharedBehaviour : MonoBehaviour {

	public static SharedBehaviour current;

	public Transform somePrefab;

	public int score;
	public string playerName;

	void Awake()
	{
		if(current != null && current != this)
		{
			Destroy(gameObject);
		}
		else
		{
			DontDestroyOnLoad(gameObject);
			current = this;
		}
	}

}

Or in JS like this:

	static var current : NameOfYourScript;

	var somePrefab : Transform;

	var score : int;
	var playerName : String;

	function Awake()
	{
		if(current != null && current != this)
		{
			Destroy(gameObject);
		}
		else
		{
			DontDestroyOnLoad(gameObject);
			current = this;
		}
	}

This ensures that no matter how many scenes the object is in (useful for testing) there's only ever one copy in memory. You'd access the variables like this:

var x = SharedBehaviour.current.somePrefab;

Storing more complicated things

Ok so we had a look at a high score method using PlayerPrefs - what a total load of rubbish that was - having to know it was HighScore1Name - ugh. We'd be better off defining a high score table like this:

public class ScoreEntry
{
     public string name;
     public int score;
}

public List<ScoreEntry> highScores = new List<ScoreEntry>();

//Using it

highScores.Add(new ScoreEntry { name = currentPlayerName,
       score = score });

//Displaying it

void OnGUI()
{
     foreach(var score in highScores)
     {
         GUILayout.Label(string.Format("{0} : {1:#,0}",
            score.name, score.score));
     }
}
class ScoreEntry
{
     var name : String;
     var score : int;
}

var highScores = new List.<ScoreEntry>();

//Using it
var entry = new ScoreEntry();
entry.name = currentPlayerName;
entry.score = score;
highScores.Add(entry);

//Displaying it

function OnGUI()
{
     for(var score in highScores)
     {
         GUILayout.Label(String.Format("{0} : {1:#,0}",
            score.name, score.score));
     }
}

But how would we store that in PlayerPrefs - well to the rescue is BinaryFormatter! With BinaryFormatter we can convert any class with a parameterless constructor into a byte array and from there into a string - suitable for storing in PlayerPrefs.  You just have to include a couple of namespaces and that's all there is to it:

//You must include these namespaces
//to use BinaryFormatter
import System;
import System.Runtime.Serialization.Formatters.Binary;
import System.IO;

class ScoreEntry
{
     var name : String;
     var score : int;
}

//High score table
var highScores = new List.<ScoreEntry>();

function SaveScores()
{

	//Get a binary formatter
	var b = new BinaryFormatter();
	//Create an in memory stream
	var m = new MemoryStream();
	//Save the scores
	b.Serialize(m, highScores);
	//Add it to player prefs
	PlayerPrefs.SetString("HighScores", 
		Convert.ToBase64String(
			m.GetBuffer()
		)
	);
}

function Start()
{
	//Get the data
	var data = PlayerPrefs.GetString("HighScores");
	//If not blank then load it
	if(!String.IsNullOrEmpty(data))
	{
		//Binary formatter for loading back
		var b = new BinaryFormatter();
		//Create a memory stream with the data
	var m = new MemoryStream(Convert.FromBase64String(data));
	//Load back the scores
	highScores = b.Deserialize(m) as List.<ScoreEntry>;
}
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Collections;
//You must include these namespaces
//to use BinaryFormatter
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class HighScores : MonoBehaviour {

	//High score entry
	public class ScoreEntry
	{
		//Players name
	     public string name;
		//Score
	     public int score;
	}

	//High score table
	public List<ScoreEntry> highScores = new List<ScoreEntry>();

	void SaveScores()
	{
		//Get a binary formatter
		var b = new BinaryFormatter();
		//Create an in memory stream
		var m = new MemoryStream();
		//Save the scores
		b.Serialize(m, highScores);
		//Add it to player prefs
		PlayerPrefs.SetString("HighScores", 
			Convert.ToBase64String(
				m.GetBuffer()
			)
		);
	}

	void Start()
	{
		//Get the data
		var data = PlayerPrefs.GetString("HighScores");
		//If not blank then load it
		if(!string.IsNullOrEmpty(data))
		{
			//Binary formatter for loading back
			var b = new BinaryFormatter();
			//Create a memory stream with the data
			var m = new MemoryStream(Convert.FromBase64String(data));
			//Load back the scores
			highScores = (List<ScoreEntry>)b.Deserialize(m);
		}
	}

}

Storing Unity Objects

It can be tricky to store actual Unity objects so there's often a need to create a class that mirrors the data in a Unity object that you actually use for storage.  So for example if you wanted to pass a bunch of AnimationStates across an RPC call then you will need to create a class that represents the transmitted AnimationState.  The same is true for a bunch of other Unity objects, but the work is trivial.

Sending Via RPC

So clearly the last code managed to turn any old class into a string, so we can use the same principle to send any kind of parameter via RPC!

It's not documented but RPC can also send a byte[], which we can get directly out of our BinaryFormatter, saving us considerable time converting it to and from a string.
Adding the ability to send our highscores to another player is suddenly very very easy:

	[RPC]
	void ReceiveHighScores(byte[] highScores)
	{
		var b = new BinaryFormatter();
		var m = new MemoryStream(highScores);
		var otherPlayersScores = (List<ScoreEntry>)b.Deserialize(m);
	}

	void UpdateScores()
	{
		var b = new BinaryFormatter();
		var m = new MemoryStream();
		b.Serialize(highScores, m);
		networkView.RPC("ReceiveHighScores", RPCMode.Others, m.GetBuffer());
	}

Talking to the web

Sending data to web servers using WWWForm is easy, but it can be easiest to convert the binary data into a string as we saw with Convert.ToBase64String() although it's not necessary depending on how you configure your server.

Saving to a file

Saving to a file is also easy, Unity provides Application.persistentDataPath as a location for your files and you just use the FileStream in place of the MemoryStream.

void SaveScores()
	{
		//Get a binary formatter
		var b = new BinaryFormatter();
		//Create a file
		var f = File.Create(Application.persistentDataPath + "/highscores.dat");
		//Save the scores
		b.Serialize(f, highScores);
                f.Close();
	}

	void Start()
	{
		//If not blank then load it
		if(File.Exists(Application.persistentDataPath + "/highscores.dat"))
		{
			//Binary formatter for loading back
			var b = new BinaryFormatter();
			//Get the file
			var f = File.Open(Application.persistentDataPath + "/highscores.dat", FileMode.Open);
			//Load back the scores
			highScores = (List<ScoreEntry>)b.Deserialize(f);
			f.Close();
		}
	}

Conclusion

Hopefully you've learned a few new tricks to save data and move it between different instances of the same game, be that as a stored value or via an RPC connection.  Remember if you want to save complicated Unity scenes and prefab instances you want to check out Unity Serializer.

, , , , , , ,

36 Responses to "Saving Data #1 – Remember Me?"

  • tim
    November 15, 2012 - 4:19 pm Reply

    Thanks for the useful tutorial!

    Seems like a List via the BinaryFormatter should also work with PreviewLabs’ version of PlayerPrefs.
    http://www.previewlabs.com/writing-playerprefs-fast/

    Either way, it’s just sending a string to whatever version of PlayerPrefs.cs.

    • whydoidoit
      November 15, 2012 - 4:20 pm Reply

      Yes I can’t see any reason why it wouldn’t work with that – it is just a string.

  • SongU
    November 28, 2012 - 3:14 pm Reply

    on Android

    ———————————————————————–
    highScores = (List)b.Deserialize(m);
    ———————————————————————–

    this line play well?

    i have problem about that line on android..

  • whydoidoit
    November 28, 2012 - 10:30 pm Reply

    It should – that is (List<ScoreEntry>)b.Deserialize(m) – what is the error?

  • SongU
    November 28, 2012 - 11:25 pm Reply

    when i build unity/android that is not error, but i test use myphone just it stop

    so i build in eclipse and i find out ‘fileread error’..

    but i dont know about more specific..

    i just ask this script well play in android

    • tim
      November 29, 2012 - 4:01 pm Reply

      Can you post a stack trace from ADB? Or post it on Unity Forums / Answers & link to it here?

  • whydoidoit
    November 29, 2012 - 7:09 am Reply

    Well I don’t develop for Android so I cannot be 100% sure why you are having a problem – I can’t think why it would be a problem!

  • Liam
    December 28, 2012 - 7:08 am Reply

    Ahhh the BinaryFormatter is wonderful. I recently had to save heaps of bools and ended up storing them in a string where each character in the string was an 8-bit bitstring. I could have just stored them in a class and used this method. So much easier on the eyes and would have saved time.

    In the C# example where you save the ScoreEntry list you’ve included “using System.Linq”, but I dont see anything that requires linq. Am I missing something or is that just a habit of yours?

  • whydoidoit
    December 31, 2012 - 11:14 am Reply

    Yeah – just a habit I’m afraid :)

  • SongU
    January 10, 2013 - 4:43 am Reply

    if you get a message in ‘Serialize()’

    ExecutionEngineException: Attempting to JIT compile method ‘List`1__TypeMetadata1:.ctor ()’ while running with –aot-only.

    this link help you
    http://forum.unity3d.com/threads/140606-iOS-Basic-BinaryFormatter

    player settings – other setting – optimization – stripping Level – use micro mscorlib

  • Mike Davis
    January 13, 2013 - 4:13 pm Reply

    This is a fantastic tutorial – quick question:

    In your playerScores example – what if you didnt want to print out the entire list of player scores, but you just wanted to access one set of scores. Lets say you had a third variable, player team, inside the original class HighScores, and you wanted to just print that team name, plus their player names and scores. How would you do that exactly?

  • Mike Davis
    January 13, 2013 - 8:17 pm Reply

    Very nice overview and well explained. Quick question: In your example of the highscores class – what if you wanted to break out the deserialized information. For example, if you wanted to get just a specific name and score?

    Could you somehow put this information into a for loop and get just the name and score from a specific index?

    I guess I dont know enough about how the data is stored in the string to understand how that would work.

  • whydoidoit
    January 16, 2013 - 2:41 pm Reply

    I’d use Linq for that – something like:

    using System.Linq;

    foreach(var score in highScores.Where(sc=>sc.teamName == theTeamToSelect))
    {
    //The scores for the team
    }

    var playersScores = highScores.Where(sc=>sc.playerName == thePlayerToSelect).ToArray();

  • Tanveer
    January 22, 2013 - 6:06 am Reply

    its make saving much easier,
    worked prefect !!
    thanks

  • Leo
    March 5, 2013 - 1:04 pm Reply

    I am having issues serializing this class, I keep getting this:

    SerializationException: Type Store+StoreItem is not marked as Serializable.

    //Store Item Class
    public class StoreItem
    {
    //name
    public string name_;

    //Description
    public string description_;

    //Cost
    public int cost_;

    //Catergory
    public string category_;

    //isConsumable
    public string is_consumable_;

    //numberInStock
    public int number_in_stock_;

    //Has Been Purchased
    public string has_been_purcased_;

    //Is Enabled
    public string is_enabled_;

    //Upgrade Level
    public int upgrade_level_;
    }

    • Leo
      March 5, 2013 - 1:06 pm Reply

      Thanks for the great guide BTW. :)

      • whydoidoit
        March 5, 2013 - 1:48 pm Reply

        Hmmm, normally that’s ok but if its saying that put [Serializable] in front of the class definition

    • Janaka
      March 6, 2013 - 6:12 am Reply

      adding [Serializable()] to ScoreEntry should fix this

      • Leo
        March 6, 2013 - 4:02 pm Reply

        Awesome! It worked. Thanks

  • Janaka
    March 6, 2013 - 6:14 am Reply

    I have tried the example on iPad with writing to PlayerPrefs. There are no run time errors but when I close the app and start again I don’t get the values back.

    Any idea how to fix this?

    • whydoidoit
      March 6, 2013 - 7:48 am Reply

      You need to save PlayerPrefs itself with PlayerPrefs.Save

  • jeev
    March 10, 2013 - 7:28 am Reply

    Hey!
    Just wanted to say, awesome tuts man, keep up the good work!

  • Chan
    April 3, 2013 - 6:48 pm Reply

    I tried copying and pasting the code from “Saving to a file” section in a script to see if it would take, and I got errors. Was there more to it?

  • Henri
    July 11, 2013 - 8:05 am Reply

    To save space when sending a byte array over the network, send memoryBuffer.ToArray() instead of memoryBuffer.GetBuffer(). The GetBuffer() function returns the entire byte array buffer, including the unused bytes.

  • lala
    August 3, 2013 - 4:16 pm Reply

    i m getting this error when try to write file…
    —————————————————————
    SerializationException: Type AlarmEntry is not marked as Serializable.
    —————————————————————
    from my code (c#)
    private List alarms ;
    void Start() {
    alarms = new List();
    // t1 and t2 are two DateTime values
    alarms.Add(new AlarmEntry{alarmName=”TEST”,timeStart=t1,timeEnd=t2,themeName=”crossFire”,themeChar=”gogo”});
    }

    void writePrefFile() {
    Debug.Log(pfile);
    //PlayerPrefs.SetString(“alarm1″,eventValue);
    //Get a binary formatter
    var b = new BinaryFormatter();
    //Create a file
    var f = File.Create(pfile);
    //Save the scores
    b.Serialize(f, alarms);
    f.Close();
    }

    any ideas.. ?? btw, my AlartSetting class is like below
    public class AlarmEntry
    {
    public String alarmName;
    public DateTime timeStart;
    public DateTime timeEnd;
    public String themeName;
    public String themeChar;
    }

  • lala
    August 3, 2013 - 5:41 pm Reply

    ok, i solve it by declaring AlarmEntry class serializable
    i.e [Serializable] public class AlarmEntry …..
    huge thanks, this is so awesome..

  • Orlando
    November 4, 2013 - 8:26 pm Reply

    Hi, thanks for this tutorial. I have a question: what method you recommend to save and retrieve data continuously without affecting performance. I mean, what is the faster way to save and retrieve data, including arrays? Thanks

    • whydoidoit
      November 24, 2013 - 8:58 pm Reply

      I’d suggest that you make a copy in memory and save it on a second thread to minimise the overhead. Unity Serializer is pretty fast after the first pass against a data structure, BinaryFormatter is ok – sometimes slower.

  • Joseph.Botros
    December 1, 2013 - 10:13 pm Reply

    Hello,

    I’m trying to make a level editor for players so I need to save whatever the player put in their custom levels, my question is:
    Is it possible to serialize a whole game objects with their components and keeps any references in these components to other components in other game objects?
    For example: a hinge on game object X is attached to the rigidbody of game object Y.

  • Then I should...
    December 20, 2013 - 12:44 pm Reply

    I’m to understand it’s wise to create parameter-less constructors? Or is an overloaded constructor acceptable as my constructor with arguments only sets some variables?

  • Oscar Kurniawan
    December 23, 2013 - 5:27 am Reply

    Hi,

    What kind of approach we can use against wp8/windows 8 build?

    The serialization method above is not working since they don’t have those method/class available in the library. Thank you…

    nb: is your unity asset working with wp8/windows 8?

  • Zeeshan
    March 3, 2014 - 7:26 pm Reply

    Great Tutorial….

  • Kevin
    May 9, 2014 - 9:52 am Reply

    I have some state for my game
    And I want to save score of state when I complete a state!

    state1 – score: 100
    state2 – score: 200

    How can I do that?

  • Thomas
    May 30, 2014 - 7:40 pm Reply

    I’ve got a strange problem with this script.

    It is working perfectly as long as I stay within one scene ( even if games is beeing restarted ). Data is beeing serialized and deserialized with no issue.
    But when I try to serialize is in one scene, pass it through PlayerPrefs and deserialize it in another scene i get “InvalidCastException: Cannot cast from source type to destination type.”

    I’m sure there is no error in function cause I’ve got the same problem even if i copy and paste it into both sceenes. I also checked string in both sceenes (before setting and after getting the string from PlayerPrefs ) and they are both the same.

    Honestly speaking I’m running out of ideas what could be the reason of that. Have you got any ?

  • home cleaners
    June 26, 2014 - 4:04 am Reply

    This is my first time pay a visit at here and i am really
    pleassant to read everthing at alone place.

Leave a Reply

%d bloggers like this: