- Home /
Serialization is incorrect on Android
Hello there. I want to serialize game data for an Android game. I had to change some things about my first script that was working, but now I have problems with that script. I am sure there is a much smoother way of doing this:
I want to store my data in a "DataHandleScript" at runtime.
I want to store my data in serialized binary between game sessions.
I tried to strip this whole thing down into one script, but I had some problems. I wanted to be able to call all variables without reference (-> static variables would be easy), but they are supposed to be serializable (so static is not possible).
Example:
SaveValues.highscore = x; //or SaveValues.Instance.highscore
SaveValues.Save();
I've tried following multiple tutorials, but they didn't guide me to the solution I wanted, therefore I tried changing their suggestions a little. Only problem is that if I load another scene or reopen the app, the values have randomly increased a lot (e.g. from 115 scored points to 22.465).
This is the main part of my Saving Script:
[System.Serializable]
public static class SavingValues
{
public static SaveValues Instance;
void Awake(){
Instance = this;
}
public void Load()
{
if (File.Exists(Application.streamingAssetsPath+"/SaveFile.bfp"))
{
SaveValues.Instance.data = SaveData.Load(Application.persistentDataPath+"/"+SaveValues.Instance.fileName+".bfp");
SaveValues.Instance.highscore = SaveValues.Instance.data.GetValue<int>("Highscore");
SaveValues.Instance.collectedPoints = SaveValues.Instance.data.GetValue<int>("CollectedPoints");
SaveValues.Instance.shopUnlocked = SaveValues.Instance.data.GetValue<bool>("ShopUnlocked");
SaveValues.Instance.lifetimeScore = SaveValues.Instance.data.GetValue<int>("LifetimeScore");
}
}
public void Save()
{
SaveValues.Instance.data = new SaveData(SaveValues.Instance.fileName);
SaveValues.Instance.data["Highscore"] = SaveValues.Instance.highscore;
SaveValues.Instance.data["CollectedPoints"] = SaveValues.Instance.collectedPoints;
SaveValues.Instance.data["ShopUnlocked"] = SaveValues.Instance.shopUnlocked;
SaveValues.Instance.data["LifetimeScore"] = SaveValues.Instance.lifetimeScore;
SaveValues.Instance.data.Save();
}
public void Reset(bool firstStartDone)
{
SaveValues.Instance.data = new SaveData(SaveValues.Instance.fileName);
SaveValues.Instance.data["Highscore"] = 0;
SaveValues.Instance.data["CollectedPoints"] = 0;
SaveValues.Instance.data["ShopUnlocked"] = false;
SaveValues.Instance.data["LifetimeScore"] = 0;
SaveValues.Instance.data["FirstStartDone"] = firstStartDone;
SaveValues.Instance.data.Save();
}
And this is my DataHandleClass:
[System.Serializable]
public class SaveValues
{
public int highscore = 0; //saved Highscore
public int collectedPoints = 0; //Points collected on this account
public bool shopUnlocked = false; //Is shop unlocked
public int lifetimeScore = 0; //count of all points ever collected
public string fileName = "SaveFile"; //name of the save file
private SaveData data; //save data variable
}
in the same script. I hope you can show me a better way of archieving my goal or find the reason for this script to strangely increasing my values. Please help me, I have made great progress in my game but I'm stuck with this problem for a few weeks now and I can't find a proper solution.
Thanks in advance.
There are alot strange / odd things in your code:
You have a static class "SavingValues" and have the attribute Serializeble attached. That makes no sense since a static class can only contain static members. You can't have an instance of a static class and therefore it can't be serialized.
That static class seems to contain non static members like Awake, Load, Save, Reset. That wouldn't even compile. Given those names i guess that class should be a non static $$anonymous$$onoBehaviour derived class?
Your "SaveValues" class contains a field "fileName". Are you sure you want to store the filename inside the savegame? It seems a bit odd to serialize the filename inside the file itself.
You use
SaveValues.Instance
but theSaveValues
class doesn't have anInstance
. YourSavingValues
class does.This line
Instance = this;
in awake won't work since your would try to assign aSavingValues
instance (given that you removed the static from the class declaration) to a variable of typeSaveValues
.What is actually SaveData? It seems you have a mix of 3 classes (where SaveData is missing completely) which actually should represent 2.
You are right, there are a few mistakes in my question, I had to scratch a few parts of the script that were messy remains of a script that was altered completely nearly a hundret times :D Thanks for the heads up!
Answer by BugFighter · Mar 03, 2016 at 12:25 PM
I've figured it out. My problem wasn't this script (although It would have been a problem too ^^). In a FixedUpdate()-Function of mine a script has done this:
void FixedUpdate ()
{
if (gameover == true)
{
scoreText.text = "Score: " + score;
GameOverText.text = "GAME OVER";
FinalScoreText.text = "You've earned " + score + " points.";
Instance.collectedPoints += score;
SaveValues.Save();
}
}
Which ultimately leads to a repeated addition of the score to the collectedPoints variable until I load another scene. I've fixed that and now it works!
Thanks though, @Bunny83, your answers were extremely helpful too.
Answer by Bunny83 · Dec 22, 2015 at 01:18 PM
A common implementation is to use a simple singleton class for both, storing the values with a BinaryFormatter and provide global access to those values.
//SaveLoadManager.cs
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[System.Serializable]
public class SaveValues
{
public int highscore = 0;
public int collectedPoints = 0;
public bool shopUnlocked = false;
public int lifetimeScore = 0;
public bool FirstStartDone = false;
private static string m_DefaultFileName = "SaveFile.bfp";
private static SaveValues m_Instance = null;
public static SaveValues Instance
{
get
{
if (m_Instance == null)
{
if (!Load())
{
m_Instance = new SaveValues();
Reset();
}
}
return m_Instance;
}
}
public static void Save()
{
Save(Application.streamingAssetsPath + "/" + m_DefaultFileName);
}
public static void Save(string aFileName)
{
using (var stream = File.OpenWrite(aFileName))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, Instance);
}
}
public static bool Load()
{
return Load(Application.streamingAssetsPath + "/" + m_DefaultFileName);
}
public static bool Load(string aFileName)
{
if (!File.Exists(aFileName))
return false;
using (var stream = File.OpenRead(aFileName))
{
var formatter = new BinaryFormatter();
m_Instance = (SaveValues)formatter.Deserialize(stream);
}
return true;
}
public static void Reset()
{
Instance.highscore = 0;
Instance.collectedPoints = 0;
Instance.shopUnlocked = false;
Instance.lifetimeScore = 0;
}
}
public class SaveLoadManager : MonoBehaviour
{
void Awake()
{
SaveValues.Load();
}
}
Here you can access your values from everywhere using:
SaveValues.Instance.highscore = 6;
If you don't like the "Instance" in between, you can add static properties for all your values like that:
public static int highscore
{
get { return Instance.highscore; }
set {Instance.highscore = value; }
}
//...
edit
Fixed an issue that would result in a stack overflow when the save files doesn't exist yet ^^.
That looks very promising! I'll give it a try and see if it fits my needs, thanks.
@Bunny83 I still have a few questions about exceptions I get.
If I use
Application.strea$$anonymous$$gAssetsPath
, the error message reads something like "path .../Assets/StreamedAssets/SaveFile.bfp could not be found", but since I don't callSaveValues.Load()
onAwake()
, but later on – only as I checked if the file exists (which gives me an exception too; weird?). – shouldn't this error not occur then? Also why do I get an error if I only ask IF the file was existing? Shouldn't it just return false? Also, I thought thatApplication.strea$$anonymous$$gAssetsPath
is compiled and therefore not editable for a mobile device or is that just an urban legend :D?The console gives me an error that says that the "field data is not found in the class SaveValues". I thought you just need to input data to be serialized, which in my case would be
Instance
.
Thanks again for your patience ^^
@Bunny83 Actually, I've checked the error again. It says: DirectoryNotFoundException: Could not find a part of the path "/Users/username/Unity Project/Assets/Strea$$anonymous$$gAssets/SaveFile.bfp" and origins from the line
if(File.Exists(Application.strea$$anonymous$$gAssetsPath + "/" + m_defaultFileName)
I just realised that you used strea$$anonymous$$gAssets. That path is used for assets that you provide inside the special folder strea$$anonymous$$gassets in your project. Those assets won't be packed into Unity's internal assetformat but are simply copied into the build folder.
If you don't have any strea$$anonymous$$g assets in your game, that folder most likely isn't created at all. Of course you could create that folder yourself, but it's actually not ment to be used for storing savegames. You should use Application.persistentDataPath which is ment as permanent storage location for your game.