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!
[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.

November 15, 2012 - 4:19 pm
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.
November 15, 2012 - 4:20 pm
Yes I can’t see any reason why it wouldn’t work with that – it is just a string.
November 28, 2012 - 3:14 pm
on Android
———————————————————————–
highScores = (List)b.Deserialize(m);
———————————————————————–
this line play well?
i have problem about that line on android..
November 28, 2012 - 10:30 pm
It should – that is (List<ScoreEntry>)b.Deserialize(m) – what is the error?
November 28, 2012 - 11:25 pm
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
November 29, 2012 - 4:01 pm
Can you post a stack trace from ADB? Or post it on Unity Forums / Answers & link to it here?
November 29, 2012 - 7:09 am
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!
December 28, 2012 - 7:08 am
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?
December 31, 2012 - 11:14 am
Yeah – just a habit I’m afraid
January 10, 2013 - 4:43 am
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
January 13, 2013 - 4:13 pm
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?
January 13, 2013 - 8:17 pm
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.
January 16, 2013 - 2:41 pm
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();
January 22, 2013 - 6:06 am
its make saving much easier,
worked prefect !!
thanks
March 5, 2013 - 1:04 pm
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_;
}
March 5, 2013 - 1:06 pm
Thanks for the great guide BTW.
March 5, 2013 - 1:48 pm
Hmmm, normally that’s ok but if its saying that put [Serializable] in front of the class definition
March 6, 2013 - 6:12 am
adding [Serializable()] to ScoreEntry should fix this
March 6, 2013 - 4:02 pm
Awesome! It worked. Thanks
March 6, 2013 - 6:14 am
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?
March 6, 2013 - 7:48 am
You need to save PlayerPrefs itself with PlayerPrefs.Save
March 10, 2013 - 7:28 am
Hey!
Just wanted to say, awesome tuts man, keep up the good work!
April 3, 2013 - 6:48 pm
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?