Hello everybody!
Accordingly with the live training archive 20. Persistence - Saving and Loading Data
I’ve made this very basic script below and it works fine.
Abstract
In Awake() Load() is called, if not persistentData exists Load() calls InitializePersistentData(), then if you press button num value increase and gets stored.
Now I can exit and re-enter game and Awake() calls Load() and finds persistentData and load it.
But if in a next game update I’ll need some new variables to be persistent ( i.e. after public int num; add public string str; ), how can I handle it?
Thanks!
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
//BEGIN OF Test CLASS
public class Test : MonoBehaviour
{
public int num;
//.dat is arbitrary
//You can use whatever file extension You like
//or not put an extension at all
private const string _fileNameForPersistentData = "/whatEverNameIwant.dat";
//BEGIN OF Awake() FUNCTION
void Awake()
{
Load();
}
//END OF Awake() FUNCTION
//BEGIN OF OnGUI() FUNCTION
void OnGUI()
{
if( GUI.Button( new Rect( 10, 10, 150, 100 ), "num = " + num.ToString() ) )
{
num++;
Save();//call Save() to persistent store this value
}
if( GUI.Button( new Rect( 10, 130, 150, 100 ), "Reset num" ) )
{
InitializePersistentData();
}
}
//END OF OnGUI() FUNCTION
//BEGIN OF InitializePersistentData FUNCTION
public void InitializePersistentData()
{
num = 0;
}
//END OF InitializePersistentData FUNCTION
//BEGIN OF REGION Save() Load()
#region Save() Load()
//BEGIN OF Save() FUNCTION
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create( Application.persistentDataPath
+ _fileNameForPersistentData );
GameData data = new GameData();
data.numToSerialize = num;
bf.Serialize( file, data );
file.Close();
}
//END OF Save() FUNCTION
//BEGIN OF Load() FUNCTION
public void Load()
{
if( !File.Exists( Application.persistentDataPath
+ _fileNameForPersistentData ) )
{
InitializePersistentData();
}
if( File.Exists( Application.persistentDataPath
+ _fileNameForPersistentData ) )
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open( Application.persistentDataPath
+ _fileNameForPersistentData, FileMode.Open );
GameData data = ( GameData )bf.Deserialize( file );
file.Close();
num = data.numToSerialize;
}
}
//END OF Load() FUNCTION
#endregion Save() Load()
//END OF REGION Save() Load()
}
//END OF Test CLASS
//BEGIN OF [Serializable] GameData CLASS
[Serializable]
class GameData
{
public int numToSerialize;
//BEGIN OF NumToSerializeToSerialize PROPERTY
bool NumToSerialize
{
set
{
numToSerialize = 0;
}
}
//END OF NumToSerializeToSerialize PROPERTY
}
//END OF [Serializable] GameData CLASS
Do note that while using the default binary serialiser might be easy, it also ties close to your code structure, making later refactors of that code impractical to say the least. As an example, putting your class in a namespace makes reading back data serialised before the namespacing fail.
If you don’t have a lot of data I would still use PlayerPrefs to ensure that a healthy level of abstraction is kept, plus it handles storage for you as well.
If you do have a lot of data, just keep the above limitation in mind and make sure that your structure is abstract enough to most likely make sense in future iterations of your codebase.
This is why I always have a class to store data that is explicitly for serialization. If I’m saving a character’s attributes, then he will not store his HP, MP, strength, etc in his class, but will have a “PlayerData” class and a member of that type, and that’s the one that gets serialized.
I added an int[ ] array, everything works but [OptionalField] on that array.
Very frustrating → no chance for any game update.
Can someone help me how to figure out Version tolerance with array please?
Thanks Everybody
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;//required by [OptionalField]
//BEGIN OF Test CLASS
public class Test : MonoBehaviour
{
public int num;
public string str;
public int[] array;
//.dat is arbitrary
//You can use whatever file extension You like
//or not put an extension at all
private const string _fileNameForPersistentData = "/whatEverNameIwant.dat";
//BEGIN OF Awake() FUNCTION
void Awake()
{
array = new int[10];
Load();
}
//END OF Awake() FUNCTION
//BEGIN OF OnGUI() FUNCTION
void OnGUI()
{
int i = 0;
if( GUI.Button( new Rect( 10, 10, 250, 100 ), str
+ " = "
+ num.ToString()
+ " array = "
+ array[i].Tostring() ) )
{
num++;
str = "numFromStr";
for( ; i < 10; i++ )
{
array[i] = ( num * 2 );
}
Save();//call Save() to persistent store this value
}
if( GUI.Button( new Rect( 10, 130, 250, 100 ), "Reset num" ) )
{
InitializePersistentData();
}
}
//END OF OnGUI() FUNCTION
//BEGIN OF InitializePersistentData FUNCTION
public void InitializePersistentData()
{
num = 0;
str = "";
for( int i = 0; i < 10; i++ )
{
array[i] = 0;
}
}
//END OF InitializePersistentData FUNCTION
//BEGIN OF REGION Save() Load()
#region Save() Load()
//BEGIN OF Save() FUNCTION
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create( Application.persistentDataPath
+ _fileNameForPersistentData );
GameData data = new GameData();
data.numToSerialize = num;
data.strToSerialize = str;
data.arrayToSerialize = new int[10];
for( int i = 0; i < 10; i++ )
{
data.arrayToSerialize[i] = array[i];
}
bf.Serialize( file, data );
file.Close();
}
//END OF Save() FUNCTION
//BEGIN OF Load() FUNCTION
public void Load()
{
if( !File.Exists( Application.persistentDataPath
+ _fileNameForPersistentData ) )
{
InitializePersistentData();
}
if( File.Exists( Application.persistentDataPath
+ _fileNameForPersistentData ) )
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open( Application.persistentDataPath
+ _fileNameForPersistentData, FileMode.Open );
GameData data = ( GameData )bf.Deserialize( file );
file.Close();
num = data.numToSerialize;
str = data.strToSerialize;
for( int i = 0; i < 10; i++ )
{
array[i] = data.arrayToSerialize[i];
}
}
}
//END OF Load() FUNCTION
#endregion Save() Load()
//END OF REGION Save() Load()
}
//END OF Test CLASS
//BEGIN OF [Serializable] GameData CLASS
[Serializable]
class GameData
{
public int numToSerialize;
[OptionalField]
public string strToSerialize;
[OptionalField]
public int[] arrayToSerialize;
//BEGIN OF NumToSerializeToSerialize PROPERTY
int NumToSerialize
{
set
{
numToSerialize = 0;
}
}
//END OF NumToSerializeToSerialize PROPERTY
//BEGIN OF StrToSerialize PROPERTY
string StrToSerialize
{
set
{
strToSerialize = "";
}
}
//END OF StrToSerialize PROPERTY
//BEGIN OF ArrayToSerialize PROPERTY
int[] ArrayToSerialize
{
set
{
arrayToSerialize = new int[10];
}
}
//END OF ArrayToSerialize PROPERTY
}
//END OF [Serializable] GameData CLASS
Hi Mike!
Yes, I’ve got null ref etc. Error 'cause deserialize does not find the new array in the binary and does not ignore it even if it is signed as [OptionalField].
It happens on every new field as long as it is an array, no matter which type of course.
Any help will be appreciated!
Thanks
Thank you.
Very interesting approach, but unfortunately it seems to save only the first variable: int num, no matter which version you choose in the Inspector.
I made a couple of changes in your code in order to get a little more verbose OnGUI button ( line 55-61 ),
and Debug.Log in Save() and Load() just to see if it load and save the right version ( and it does! )
Thanks
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;//required by [OptionalField]
#region Versions-Updates GameData
[Serializable]
public class GameData00{
public int numToSerialize;
}
[Serializable]
public class GameData01 : GameData00{
//New variable added
public string strToSerialize;
}
[Serializable]
public class GameData02 : GameData01{
//New variable added
public int[] arrayToSerialize;
}
#endregion
public class Test : MonoBehaviour {
public bool ResetInit = false;
public enum Version{_00,_01,_02};
public Version version = Version._00;
public int num;
public string str;
public int[] array = new int[10]; //Fixed length array
private string _fileNameForPersistentData = "/whatEverNameIwant.dat";
void Awake() {
if(ResetInit) DeletePersistentData(); //Reset GameData type file
Load();
}
void OnGUI()
{
if( GUI.Button( new Rect( 10, 10, 250, 100 ), "save " + str + " + " + num ) )
{
num++;
str = "TEST";
for( int i = 0; i < 10; i++ ) array[i] = i;
Save();//call Save() to persistent store this value
}
if( GUI.Button( new Rect( 10, 130, 250, 100 ), "Delete GameData" ) ) DeletePersistentData();
}
void InitializePersistentData()
{
num = 0;
str = "";
for( int i = 0; i < 10; i++ ) array[i] = 0;
}
void DeletePersistentData(){
if( File.Exists( Application.persistentDataPath + _fileNameForPersistentData ) ){
File.Delete(Application.persistentDataPath + _fileNameForPersistentData);
InitializePersistentData();
}
}
#region Save() Load()
//BEGIN OF Save() FUNCTION
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create( Application.persistentDataPath
+ _fileNameForPersistentData );
switch (version){
case Version._00:
GameData00 gameData00 = new GameData00();
gameData00.numToSerialize = this.num;
bf.Serialize( file, gameData00 );
break;
case Version._01:
GameData01 gameData01 = new GameData01();
gameData01.numToSerialize = this.num;
gameData01.strToSerialize = this.str;
bf.Serialize( file, gameData01 );
break;
case Version._02:
GameData02 gameData02 = new GameData02();
gameData02.numToSerialize = this.num;
gameData02.strToSerialize = this.str;
gameData02.arrayToSerialize = this.array;
bf.Serialize( file, gameData02 );
break;
}
file.Close();
Debug.Log ( "saving version = " + version );
}
//END OF Save() FUNCTION
//BEGIN OF Load() FUNCTION
void Load()
{
if( !File.Exists( Application.persistentDataPath + _fileNameForPersistentData ) ) InitializePersistentData();
else {
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open( Application.persistentDataPath
+ _fileNameForPersistentData, FileMode.Open );
System.Object data = bf.Deserialize(file);
if(data is GameData00){
GameData00 gameData00 = (GameData00)data;
this.num = gameData00.numToSerialize;
}
else if(data is GameData01){
GameData01 gameData01 = (GameData01)data;
this.num = gameData01.numToSerialize;
this.str = gameData01.strToSerialize;
}
else if(data is GameData02){
GameData02 gameData02 = (GameData02)data;
this.num = gameData02.numToSerialize;
this.str = gameData02.strToSerialize;
this.array = gameData02.arrayToSerialize;
}
file.Close();
Debug.Log ( "loading version = " + version );
}
}
//END OF Load() FUNCTION
#endregion Save() Load()
}