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 theTime.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
How to Rotate a Picture in Winforms
How to Read a Pem Rsa Private Key from .Net
How to Decode a Url Parameter Using C#
Best Practice to Return Errors in ASP.NET Web API
Add Data Annotations to a Class Generated by Entity Framework
Entity Framework Provider Type Could Not Be Loaded
Change System Date Programmatically
Have a Set of Tasks with Only X Running at a Time
How to Create Dynamic Properties in C#
Way to Have String.Replace Only Hit "Whole Words"
Converting a Base 64 String to an Image and Saving It
How to Move and Resize a Form Without a Border
Do You Have to Put Task.Run in a Method to Make It Async
Dynamically Add C# Properties at Runtime