zero's a life

An extra chance.

Persistent Data in Unity

| Comments

I’m interested in having some data remain available, even though I’m switching Scenes in Unity. In other words, I want this data to remain persistent. As you’ll see below I was tempted to use PlayerPrefs to store the data, but, according to this Unity Live Training on Data Persistence, PlayerPrefs is not the best way to make data persist over multiple Scenes. PlayerPrefs is an okay place to store non-critical data, like the audio volume, window size, full-screen state–you know preferences. I’ll go ahead and show what I believe to be the correct way to store persistent data before talking about PlayerPrefs later.

Persistent data

To safely and reliably store persistent data, the Unity tutorials suggest that you need to assign a “kinda” Singleton design patterny script to an empty GameObject that will persist across Scenes.

using UnityEngine;
using System.Collections;

public class GameControl: MonoBehaviour {
  public static GameControl control;

  public float health;
  public float experience;

  void Awake() {
    if(control == null){
      DontDestroyOnLoad(gameObject);
      control = this;
    } else if(control != this) {
      // There can be only one!
      Destroy(gameObject);
    }
  }
}

The static reference means that we can just access the values in the GameControl script.

How to access data in the GameController.

GameController.control.health = 100;

Writing persistent data to a binary file

The persistent data that’s under the control of the GameController is perfectly happy hanging out when your application is running. It will persist between Scenes when other data is wiped out by Unity’s garbage collection. But what if you want to maintain persistent data when the application is closed.

Ah hah! Finally, an answer to my nagging question about writing persistent data to plain text files, like PlayerPrefs or simply a file in a local directory. Wouldn’t a player be able to modify the data?

Enter the binary format:

// Add two more libraries
using System;
using System.Runtime.Serialization.Formatters.Binary;

Where do we save it? The persistent data path: Application.persistentDataPath. We’ll need one more library to do some input and output to files:

// Requires one more library and we're ready to get cooking with some
// methods
using System.IO;

public void Save() {
  BinaryFormatter bf = new BinaryFormatter();
  FileStream file = File.Open(Application.persistentDataPath + "/gameInfo.dat",
                              FileMode.Open);

  PlayerData data = new PlayerData();
  data.experiencePoints = experiencePoints;
  data.playerLevel = playerLevel;

  bf.Serialize(file, data);
  file.Close();
}

public void Load() {
  if(File.Exists(Application.persistentDataPath + "/gameInfo.dat")) {
    BinaryFormatter bf = new BinaryFormatter();
    FileStream file = File.Open(Application.persistentDataPath + "/gameInfo.dat",
                                FileMode.Open);
    PlayerData data = (PlayerData)bf.Deserialize(file);
    file.Close();

    experiencePoints = data.experiencePoints;
    playerLevel = data.playerLevel;
  }
}

Now we need to make the PlayerData class that we will tag with [Serializable] so Unity will know that we want to write this to a binary file at some point in the future. This affects how Unity stores the data internally, but it doesn’t really change how we interact with it.

[Serializable]
class PlayerData {
  // TODO: see about making gets and sets.
  // TODO: automate the generation of this data structure.  See:
  // http://forums.devx.com/showthread.php?170650-How-to-dynamically-add-property-to

  // Add new variables for loading and saving here.
  public int experiencePoints;
  public int playerLevel;
}

After writing up this article, I came across an older, possibly better article on Unity Gems. Please check out that write-up and all of the great content on the site. http://unitygems.com/saving-data-1-remember-me/

My full GameController data persistence Singleton script is available in this gist.

So, that’s the best solution I’ve found to date for having persistent data across scenes and play sessions. I’ve included my rough first stabs at the problem below. Keep in mind that anything following this point is just included just to make my notes available, and it’s not meant to be a guide.

Old notes: PlayerPrefs

In order to have persistent data between Scenes, I was previously going to make use of Unity’s PlayerPrefs, a class that implements persistent data storage for ints, floats, and strings. Notably absent are useful data types like Vector3 to maintain, for example, transform data between Scenes. Some of the solutions I found may still be helpful for more storing complex data types in PlayerPrefs.

One potential solution is to simply set a GameObject’s transform.position to some constant Vector3 value in the freshly loaded Scene. While this is possible in some applications, I’m going to assume that eventually you’ll want to transfer some dynamic position information between Scenes.

Here are two potentially helpful sources to achieve this:

http://www.theappguruz.com/tutorial/store-vector3-data-easily-using-json-parsing-possible-using-playerprefs/, based on work by Mehta Dakshil. While JSON parsing looks interesting, it requires an external .dll, JsonFx.Json.dll. And I’m not interested in external dependencies here. It may work great for you.

<http://wiki.unity3d.com/index.php?title=ArrayPrefs>, based on work by Mario Madureiera Fontes and Daniel P. Rossi. (Note: since GetVector3 depends on GetFloatArray, there could be some error due to GetFloatArray casting strings to floats. But the error introduce in the resulting Vector3 is unlikely to be noticeable.)

Actually, see a more updated version here: http://wiki.unity3d.com/index.php/ArrayPrefs2, which is based on work by Eric Haines.

According to the text, the script should be placed in the Standard Assets directory, so that it can be accessed from both C# and Boo scripts.

To see a faster implementation for mobile devices, where the data to be saved in PlayerPrefs is cached in memory, see: http://www.previewlabs.com/writing-playerprefs-fast/.

Comments