- Home /
Best way to persist data while we are in a not final version
I'm a beginner, I'm making a mobile game and I want to persist the user session in memory. So I've saw the "PERSISTENCE - SAVING AND LOADING DATA" tutorial (http://unity3d.com/es/learn/tutorials/modules/beginner/live-training-archive/persistence-data-saving-loading) and I've implemented it. But one of the problems I've found with it is that when I make a change and I delete something in the Session this exception appears:
SerializationException: Field "xxxxx" not found in class Session
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadTypeMetadata (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:691)...
I understand the problem here, I deleted one of the fields I've already saved in the class Session and now I'm trying to load it again. But my question is: Is there a better way to persist data than this one? Because if I release a version of my game and after that I want to make a change in the info I'm saving (for example, delete some parameters and add others more accurate), then it will produce this kind of problems.
Should I worry about it or there is something I'm missing?
Thanks a lot
Answer by _dns_ · Sep 30, 2014 at 03:25 PM
Hi, I faced this problem with different environments but not in Unity (yet). Searching for modern solutions in C#, I found this document about Data Contract serialization & versioning that may help you: http://msdn.microsoft.com/en-us/library/ms731138%28v=vs.110%29.aspx
Thank you so much! I'm sure it will help! I'm going to investigate it more about it
Answer by furic · May 02, 2016 at 09:09 AM
I ran into this problem too and following @dns has suggested, I found out the solution is pretty easy!
First, follow this answers and download system.runtime.serialization.dll, put it in the project, and set .Net 2.0 (not sure if it's necessary)
Then you are ready to use DataContract as:
using System.Runtime.Serialization;
[Serializable, DataContract]
public class GameSave
{
[DataMember]
public int myInt;
[DataMember]
public DateTime myDateTime;
[DataMember]
public bool myBool;
[DataMember]
public int addedIntInNewVersion;
}
Tested in Editor and it's good. May test it in mobile and update this answer later.
Could you elaborate how this helps on how this helps with different versions of the persistence data?
Answer by HypnoticOwl · Sep 04, 2015 at 01:28 PM
I realize this question is a year old, but there are still 27 people following the question and I think I found a neat solution!
Basically I use dictionaries to store variables of the same type. They can be accessed in a similar way PlayerPrefs does this:
mySaves.SetInt("VariableName", 23);
int i = mySaves.GetInt("VariableName");
My starting point was the Unity tutorial mentioned in the question. All I added were functions to convert the dictionaries to strings and vice versa and some checks whether the variable you want exists. I also made sure that exeptions when trying to read your save file are handled and added code to use the PlayerPrefs instead, when the game is run in the WebPlayer.
This is how my save controller looks now: using UnityEngine; using System; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO;
public class SaveController : MonoBehaviour {
public static SaveController control;
public string fileName = "myGame.save";
private Dictionary<string, int> ints;
private Dictionary<string, string> strings;
private SaveGame save;
private bool writeSave = false;
private BinaryFormatter formatter;
private void Awake ()
{
if (control != null && control != this)
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
control = this;
formatter = new BinaryFormatter();
if(!Load())
save = new SaveGame();
}
private bool Load()
{
if (Application.isWebPlayer)
{
// Everything is in PlayerPrefs and doesn't have to be loaded
Debug.Log("Loading everything from PlayerPrefs");
return true;
}
else if(File.Exists(Application.persistentDataPath + "/"+fileName))
{
Debug.Log ("Loading " + Application.persistentDataPath + "/"+fileName);
// Load File
FileStream file = File.Open(Application.persistentDataPath + "/"+fileName, FileMode.Open);
try
{
save = (SaveGame)formatter.Deserialize(file);
}
catch
{
Debug.LogWarning("Couldn't deserialize SaveGame. Creating new!");
save = new SaveGame();
}
file.Close();
ints = StringToIntDict(save.ints);
strings = StringToStringDict(save.strings);
return true;
}
else
{
Debug.Log ("No Saves found.");
return false;
}
}
private void Save()
{
if (Application.isWebPlayer)
{
// Everything is in PlayerPrefs and doesn't have to written to file
Debug.LogWarning("This is a WebPlayer! I shouldn't even be called!");
}
else
{
// Save dictionaries
save.strings = StringDictToString(strings);
save.ints = IntDictToString(ints);
// Write to file
FileStream file = File.Create(Application.persistentDataPath + "/" + fileName);
formatter.Serialize(file, save);
file.Close();
}
}
public void Reset()
{
if (Application.isWebPlayer)
{
PlayerPrefs.DeleteAll();
Debug.Log("Deleted PlayerPrefs");
}
else
{
ints.Clear();
strings.Clear();
save = new SaveGame();
Debug.Log("Reset SaveGame");
}
}
private void LateUpdate()
{
if(writeSave)
{
Save();
writeSave = false;
}
}
public void SetString(string id, string value)
{
if (Application.isWebPlayer)
{
PlayerPrefs.SetString(id, value);
}
else
{
strings[id] = value;
writeSave = true;
}
}
public string GetString(string id)
{
if (Application.isWebPlayer)
{
return PlayerPrefs.HasKey(id) ? PlayerPrefs.GetString(id) : "";
}
else
{
return strings.ContainsKey(id) ? strings[id] : "";
}
}
public void SetInt(string id, int value)
{
if (Application.isWebPlayer)
{
PlayerPrefs.SetInt(id, value);
}
else
{
ints[id] = value;
writeSave = true;
}
}
public int GetInt(string id)
{
if (Application.isWebPlayer)
{
return PlayerPrefs.HasKey(id) ? PlayerPrefs.GetInt(id) : 0;
}
else
{
return ints.ContainsKey(id) ? ints[id] : 0;
}
}
private Dictionary<string, int> StringToIntDict(string s)
{
Dictionary<string, int> dict = new Dictionary<string, int>();
if (s == null)
{
Debug.LogWarning("Could not create dictionary, because string is null!");
return dict;
}
string[] tokens = s.Split(new char[] { ':', ',' }, StringSplitOptions.RemoveEmptyEntries);
// Walk through each item
for (int i = 0; i < tokens.Length; i += 2)
{
dict.Add(tokens[i], int.Parse(tokens[i + 1]));
}
//Debug.Log("Loaded " + dict.Count + " ints");
return dict;
}
private Dictionary<string, string> StringToStringDict(string s)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
if (s == null)
{
Debug.LogWarning("Could not create dictionary, because string is null!");
return dict;
}
string[] tokens = s.Split(new char[] { ':', ',' }, StringSplitOptions.RemoveEmptyEntries);
// Walk through each item
for (int i = 0; i < tokens.Length; i += 2)
{
dict.Add(tokens[i], tokens[i + 1]);
}
//Debug.Log("Loaded " + dict.Count + " strings");
return dict;
}
private string IntDictToString(Dictionary<string, int> d)
{
string result = "";
foreach (KeyValuePair<string, int> pair in d)
{
result += pair.Key + ":" + pair.Value + ",";
}
// Remove the final delimiter
result = result.TrimEnd(',');
return result;
}
private string StringDictToString(Dictionary<string, string> d)
{
string result = "";
foreach (KeyValuePair<string, string> pair in d)
{
result += pair.Key + ":" + pair.Value + ",";
}
// Remove the final delimiter
result = result.TrimEnd(',');
return result;
}
}
[Serializable]
class SaveGame
{
public string strings = "";
public string ints = "";
}
I wonder how would you tackle arrays of data using the dictionaries
Answer by pacifistmaster · Aug 10, 2016 at 07:52 PM
THIS IS WHAT YOU SHOULD DO!!!
If you are modifying your fields : example :
//VERSION 1 [Serializable] class PlayerData { public int num1; public int num2; }
//VERSION 2 [Serializable] class PlayerData { public int num1; }
// You just need to delete the current file with the old Serialized Fieds, Add this to the Start() Method: //------------------------ void Start(){
if (File.Exists(Application.persistentDataPath + "/playerInfo.dat"))
{
File.Delete(Application.persistentDataPath + "/playerInfo.dat");
Debug.Log("Deleted");
}
}
And Once Deleted, just remove the previous code , ;) worked as charm for me!!! ,a
While this will work as a workaround in a development env, this is dangerous to use in production! You will delete the user's data (coins/level etc)
Right. And once more we have an example of "who needs to shout usually isn't right".
Your answer
Follow this Question
Related Questions
Member variable not persistent 0 Answers
Like PlayerPrefs but clears when game starts 2 Answers
Question on Singleton Object / Hierarchy Relationship 0 Answers
Need a persistent unique id for gameobjects 11 Answers