I am using a binary formatter in my Unity game to store the progress of the player to keep track which level(s) he unlocked. It all works fine with this code:
public static void Save()
{
LevelManager levelManager = GameObject.Find ("GameManager").GetComponent<LevelManager> ();
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create (Application.persistentDataPath + "/savedProgress.1912");
bf.Serialize(file, levelManager.levelReached);
file.Close();
}
public static void Load()
{
LevelManager levelManager = GameObject.Find ("GameManager").GetComponent<LevelManager> ();
if(File.Exists(Application.persistentDataPath + "/savedProgress.1912"))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/savedProgress.1912", FileMode.Open);
levelManager.levelReached = (int)bf.Deserialize(file);
file.Close();
}
}
It just saves and int called levelReached. After the progress is saved, there will be added a new file onto your computer, in this case "savedProgress.1912".
If I edit the file as text, it looks like this:
Whenever I replace or delete something in this file, it will be corrupted and give me the following error in Unity:
System.Runtime.Remoting.Messaging.Header[]& headers) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:104)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:179)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:136)
SaveLoadProgress.Load () (at Assets/Scripts/SaveLoadProgress.cs:25)
LevelManager.Start () (at Assets/Scripts/LevelManager.cs:16)
The problem with this is, is that when I do this, I unlock all levels. They will go back to the state in which they are in the editor, before playing it. I know that I can make them locked as standard state, but is there maybe a way to not let this happen in another way?
Edit: When I wrote this I realised what I just said, just make all the level locked by standard and the problem is fixed. Though it would still be nice to know.
EDIT: I am know using the code from this thread.
This is what I'm currently using:
DataSaver.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Text;
using System;
public class DataSaver
{
//Save Data
public static void saveData<T>(T dataToSave, string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Convert To Json then to bytes
string jsonData = JsonUtility.ToJson(dataToSave, true);
byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
//Create Directory if it does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
}
//Debug.Log(path);
try
{
File.WriteAllBytes(tempPath, jsonByte);
Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}
//Load Data
public static T loadData<T>(string dataFileName)
{
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return default(T);
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return default(T);
}
//Load saved Json
byte[] jsonByte = null;
try
{
jsonByte = File.ReadAllBytes(tempPath);
Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
//Convert to json string
string jsonData = Encoding.ASCII.GetString(jsonByte);
//Convert to Object
object resultValue = JsonUtility.FromJson<T>(jsonData);
return (T)Convert.ChangeType(resultValue, typeof(T));
}
public static bool deleteData(string dataFileName)
{
bool success = false;
//Load Data
string tempPath = Path.Combine(Application.persistentDataPath, "data");
tempPath = Path.Combine(tempPath, dataFileName + ".txt");
//Exit if Directory or File does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return false;
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return false;
}
try
{
File.Delete(tempPath);
Debug.Log("Data deleted from: " + tempPath.Replace("/", "\\"));
success = true;
}
catch (Exception e)
{
Debug.LogWarning("Failed To Delete Data: " + e.Message);
}
return success;
}
}
LevelManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Linq;
using UnityEngine.UI;
[System.Serializable]
public class LevelManager : MonoBehaviour {
public GameObject[] levelButtons;
public int levelReached = 1;
void Start()
{
//SaveLoadProgress.Load ();
LevelManager loadedData = DataSaver.loadData<LevelManager>("Progress");
if (loadedData == null)
{
return;
}
levelButtons = GameObject.FindGameObjectsWithTag ("Level").OrderBy (go => go.name).ToArray ();
for (int i = 0; i < levelButtons.Length; i++)
{
if (i + 1 > loadedData.levelReached)
{
Color greyed = levelButtons [i].GetComponent<Image> ().color;
greyed.a = 0.5f;
levelButtons [i].GetComponent<Image> ().color = greyed;
}
if (i < loadedData.levelReached)
{
Color normal = levelButtons [i].GetComponent<Image> ().color;
normal.a = 1f;
levelButtons [i].GetComponent<Image> ().color = normal;
}
}
}
}
GameController.cs
if(--LEVELCOMPLETED--)
{
LevelManager saveData = new LevelManager();
saveData.levelReached = SceneManager.GetActiveScene ().buildIndex + 1;
DataSaver.saveData(saveData, "Progress");
}
When I use these codes, the debug log shows: Saved data to...
and loaded data from..
but after the loaded data from..
, it gives me this error:
ArgumentException: Cannot deserialize JSON to new instances of type 'LevelManager.'
UnityEngine.JsonUtility.FromJson[LevelManager] (System.String json) (at C:/buildslave/unity/build/artifacts/generated/common/modules/JSONSerialize/JsonUtilityBindings.gen.cs:25)
DataSaver.loadData[LevelManager] (System.String dataFileName) (at Assets/Scripts/DataSaver.cs:77)
LevelManager.Start () (at Assets/Scripts/LevelManager.cs:17)
The file "Progress.txt" does excist. It looks like this:
{
"levelButtons": [],
"levelReached": 2
}
Are modders able to see this file when I publish it for android or apple? Do I need to encrypt this if I don't want people to be able to cheat? How do I do this?