Saving/Loading Data in Unity

Saving/loading data in Unity

This problem is known when using C# serializer. Convert the data to Json with JsonUtility then save it with the PlayerPrefs. When loading, load with the PlayerPrefs then convert the json back to class with JsonUtility.

Example class to Save:

[Serializable]
public class Save
{
public List<int> ID = new List<int>();
public List<int> Amounts = new List<int>();
public int extra = 0;
public float highScore = 0;
}

Save Data:

void Save()
{
Save saveData = new Save();
saveData.extra = 99;
saveData.highScore = 40;

//Convert to Json
string jsonData = JsonUtility.ToJson(saveData);
//Save Json string
PlayerPrefs.SetString("MySettings", jsonData);
PlayerPrefs.Save();
}

Load Data:

void Load()
{
//Load saved Json
string jsonData = PlayerPrefs.GetString("MySettings");
//Convert to Class
Save loadedData = JsonUtility.FromJson<Save>(jsonData);

//Display saved data
Debug.Log("Extra: " + loadedData.extra);
Debug.Log("High Score: " + loadedData.highScore);

for (int i = 0; i < loadedData.ID.Count; i++)
{
Debug.Log("ID: " + loadedData.ID[i]);
}
for (int i = 0; i < loadedData.Amounts.Count; i++)
{
Debug.Log("Amounts: " + loadedData.Amounts[i]);
}
}

Difference between JsonUtility.FromJson and JsonUtility.FromJsonOverwrite:

A.JsonUtility.FromJson creates new Object from Json and returns it. It allocates memory.

string jsonData = PlayerPrefs.GetString("MySettings");
//Convert to Class. FromJson creates new Save instance
Save loadedData = JsonUtility.FromJson<Save>(jsonData);

B.JsonUtility.FromJsonOverwrite does not create new Object. It has nothing to do with adding more datatype to your class. It just overwrite the data that is passed in it. It's good for memory conservation and less GC. The only time it will allocate memory if when you have fields such as array, string and List.

Example of where JsonUtility.FromJsonOverwrite should be used is when performing constant data transfer with Json. It will improve performance.

//Create Save instance **once** in the Start or Awake function
Save loadedData = null;
void Start()
{
//loadedData instance is created once
loadedData = new Save();
}

void Load()
{
string jsonData = PlayerPrefs.GetString("MySettings");
//Convert to Class but don't create new Save Object. Re-use loadedData and overwrite old data in it
JsonUtility.FromJsonOverwrite(jsonData, loadedData);
Debug.Log("High Score: " + loadedData.highScore);
}

How I can save and load level in unity3D, C# , Mobile Game?

There are many, many different ways to accomplish this. Since your question is so broad, I can only offer broad answers for you to start your research, in rough order of complexity.

PlayerPrefs: If you have a very limited amount of save data, perhaps 5-10 data points, this is a convenient library provided by Unity to set and get strings, ints, and bools. It operates differently on each platform, but "just works" for the most part.

Writing to Files: If you have more data than should be saved using PlayerPrefs, or you'd like your files to be editable/readable/transferrable by your players (or you, during development, since PlayerPrefs writes to the underlying store in binary format, typically), you can do this. Google up "reading and writing files C#" for tutorials and APIs on how to do this. You can also obfuscate these files by encrypting/decrypting them, but you'll need to be aware that converting them to a binary format has security implications, so don't go down this route (encrypting) until you know what you're doing.

Writing to [remote] databases: If you have a great deal of data (images, audio, gigs of text), then a database might be an option for you. That's very much beyond the scope of this question, however, but you should know that it's relatively easy to do this with C#.

There are other esoteric solutions, obviously, but that should get you started.

How to Save / Load game-time? (Unity Engine)

First of all why so complicated with that float[]. In my eyes it just makes it hard to read/understand and is error prone.

I would simply use a structure like the following (if you don't like it - sure stick to float[7] why not ^^)

public static class DataSerializer
{
private static readonly dataPath = Path.Combine(Application.persistentDataPath, "data.txt");

public static void SerializeData(Player player)
{
var data = new PlayerData(player);

using(var stream = File.Open(dataPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
var bf = new BinaryFormatter();
bf.Serialize(stream, data);
}
}

public static bool DeserializeData(out PlayerData data)
{
data = null;

if (!File.Exists(dataPath))
{
Debug.LogError("File does not exist.");
return false;
}

using(var stream = File.Open(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var bf = new BinaryFormatter();
data = bf.Deserialize(stream) as PlayerData;
}

return data != null;
}
}

[Serializable]
public class PlayerData
{
public SerializableVector3 Position;
public SerializableVector3 EulerAngles;
public float TotalTimePlayed;

public PlayerData(Player player)
{
Position = player.transform.position;

EulerAngles = player.transform.eulerAngles;

TotalPlayTime = player.TotalTimePlayed;
}
}

[Serializable]
public class SerializableVector3
{
public float x;
public float y;
public float z;

public SerializableVector3(Vector3 vec)
{
x = vec.x;
y = vec.y;
z = vec.z;
}

public Vector3 ToVector3()
{
return new Vector3(x,y,z);
}

public static implicit operator Vector3(SerializableVector3 vec)
{
return vec.ToVector3();
}

public static implicit operator SerializableVector3(Vector3 vec)
{
return new SerializableVector3(vec);
}
}

Then for tracking the total played time there are multiple ways.

  • Either store the last save DateTime.Now so you can simply add the time currently played when saving:

     float timePlayedThisSession = (DateTime.Now - dateTimeLastSaved).TotalSeconds;

    Pro: The system handles it and it's a one time calculation

    Con: The user can simply change the system time -> possibly "hack" your app stats

  • Or you can simply use Time.unscaledTime which anyway is the time since the last app start.

    Pro: User can not simply alter this value

    Con: Depending on your use case you might not be able to use the Time.unscaledTime if e.g. you allow to reset to the last save within your app (because in this case the Time.time simply continues counting up since app start)

  • Or you can go fully "manual" and track the played time fully on your own using Time.unscaledDeltaTime like e.g.

     private float timePlayedThisSession;

    private void Update()
    {
    timePlayedThisSession += Time.unscaledDeltaTime;
    }

    Pro: Full control + can easily reset or load a value into that field.

    Con: Overhead for the Update method being called every frame ... but this is so small that it won't matter at all! ;)

Fazit I would go with the last option and do e.g.

public class Player : MonoBehaviour
{
public GameObject player;
private float totalTimePlayed;

// Read-only access
public float TotalTimePlayed => totalTimePlayed;

void Start()
{
ResetToLastSave ();
}

private void Update ()
{
totalTimePlayed += Time.unscaledDeltaTime;
}

// Just adding this to show you could even simply add a method to allow your user revert to the last save
public void ResetToLastSave()
{
if(!DataSerializer.DeserializeData(out var loadedStats))
{
Debug.LogWarning("Could not load!", this);
return;
}

player.transform.position = loadedStats.Position;
player.transform.eulerAngles = loadedStats.Rotation;
totalTimePlayed = loadedStats.TotalTimePlayed;
}

public void Save()
{
DataSerializer.SerializeData(this);
}
}


Related Topics



Leave a reply



Submit