Hi everyone, I started making a game not long ago and I’m not that good at programming. I watched many tutorials about how to save and load informations like score etc, but I’m making a 2D platformer game and I need a way to save/load the scene aka the current position of the player, gems collected and enemies killed
Could someone help me with a script or a good tutorial that explains how to do this?
This is a common forum topic. Using JSON is a common suggestion, but the implementation of your save/load system is highly dependent on the design of your game, and specifically what actually needs to be saved/loaded to restore game state. I suggest finding some of those other threads.
I think the fastest way to get started for beginners would be to use PlayerPrefs. It requires the least setup and know-how. It’s not ideal for a lot of use-cases but it will get you off the ground really quickly.
I don’t know… I think the jump from playerprefs to json is pretty easy… if not easier than playerprefs. Especially when it comes to sticking anything more complicated than a float/int/string into PlayerPrefs, like arrays or Vectors which even beginners will run into very quickly.
Differenece…
void DoGameSaveToPlayerPrefs()
{
string level = GameManager.CurrentLevelId;
Vector3 playerPos = GetPlayer().transform.position;
int score = GameManager.Score;
int gems = GameManager.GemCount;
//annoyingly no clean simple way to save vector3 in playerprefs, so here I just stringify, will have to parse later
PlayerPrefs.SetString("save_level", level);
PlayerPrefs.SetString("save_playerpos", string.Format("{0},{1},{2}", playerPos.x, playerPos.y, playerPos.z));
PlayerPrefs.SetInt("save_score", score);
PlayerPrefs.SetInt("save_gems", gems);
}
void LoadFromPlayerPrefs()
{
var level = PlayerPrefs.GetString("save_level");
string[] playerPosString = PlayerPrefs.GetString("save_playerpos").Split(",");
var playerPos = new Vector3(float.Parse(playerPosString[0]), float.Parse(playerPosString[1]), float.Parse(playerPosString[2]));
int score = PlayerPrefs.GetInt("save_score");
int gems = PlayerPrefs.GetInt("save_gems");
SceneManager.LoadScene(level);
GetPlayer().transform.position = playerPos;
GameManager.Score = score;
GameManager.GemCount = gems;
}
Using json:
[System.Serializable]
public class SaveToken
{
public string Level;
public Vector3 PlayerPos;
public int Score;
public int GemCount;
}
void DoGameSaveJson()
{
var token = new SaveToken();
token.Level = GameManager.CurrentLevelId;
token.PlayerPos = GetPlayer().transform.position;
token.Score = GameManager.Score;
token.GemCount = GameManager.GemCount;
var path = System.IO.Path.Combine(Application.persistentDataPath, "save01.json");
System.IO.File.WriteAllText(path, JsonUtility.ToJson(token));
}
void LoadFromJson()
{
var path = System.IO.Path.Combine(Application.persistentDataPath, "save01.json");
var token = JsonUtil.FromJson<SaveToken>(System.IO.File.ReadAllText(path));
SceneManager.LoadScene(token.Level);
GetPlayer().transform.position = token.PlayerPos;
GameManager.Score = token.Score;
GameManager.GemCount = token.GemCount;
}
(note - I don’t do any validation of values or anything… this is all written here in the browser as pseudo-code for demonstration purposes)
edit: beaten to it with more complete sample code by @lordofduct . Oh well
A lot of us started with PlayerPrefs but honestly that’s just bad habits. It’s okay for saving a small number of things but once you get to the point of saving even a single Vector3 it’s already not worth the workarounds you have to use.
A custom serializable data class plus JsonUtility is honestly not difficult, and will be useful even as you learn more advanced tactics. What I mean is that, while JsonUtility itself has some limits, it’s trivial to take what you’ve learn while using JsonUtility and jumping to a more capable replacement like Json.NET (in fact, once you have the package imported, it’s literally replacing one line of code). Anything you’ve learned to get PlayerPrefs to work is absolutely, positively worthless when moving on to something more capable, and you will need to move to something more capable.
All you need is this:
// this is where you define the kinds of data you want to save
[System.Serializable]
public class GameData {
public Vector3 playerPosition;
public int gemsCollected;
//etc.
}
//need this on anything that does file save/load operations
using System.IO;
// this should be on some kind of manager class
GameData myData = new GameData();
myData.playerPosition = player.transform.position;
//etc
//to save the game
string filePath = Path.Combine(Application.persistentDataPath, "savegame.json");
File.WriteAllText(filePath, JsonUtility.ToJson(myData) );
//to load the game
string json = File.ReadAllText(filePath);
myData = JsonUtility.FromJson<GameData>(json);
Oh I agree it’s not a huge leap, but there are some common pitfalls that it is really easy for a beginner to fall into and get frustrated. For example, not setting up the Serializable data type just right will lead to frustrating silent errors where the thing simply won’t serialize with no discernible errors.
Yes, but you guys are experienced with this sort of thing. And if it goes wrong you know exactly what kind of common mistakes you might have made.
StarManta and lordofduct are right though, you should make the jump to JSON pretty quickly. I just think it can be motivating for a beginner to see the thing working quickly!~
What do you mean by just right? If your base type isn’t serializable then it will throw an exception. And once you’ve added that to one type it’s pretty easy to jump to the conclussion any other types put in there would have to be serializable as well.
Furthermore, it’s consistent with the way Unity serializes for the inspector (with the exception of UnityEngine.Object types). So it creates a consistent use case between both… with that one exception being easily explained.
Where as how do I stick an array into PlayerPrefs? Or a Vector3? Or anything aside from string/int/float. I could think of several ways that a novice would come up with to do Vector3 since it is a finite size (3 playerprefs of posX, posY, posZ being a common one I’ve witnessed… stringifying another). But array, that one is often a humdinger since it’s a dynamic size. If I had a penny every time I’ve seen a thread about dynamic length arrays and how to access/save/etc them. A smart newb might think to stringify an int array and stick commas between them… but even they’d get stuck when it’s say an array of strings…
“What about strings that contain commas?”
Yes, what about?
You quickly run into TONS of caveats. And all of which will leave a newb trying very hard to solve their problem in a manner that is fragile, not scalable, and just generally not productive in the long run.
Worse they learn many bad habits that they may carry along with them in their lessons and never purge as “bad habits” because “that’s just the way I’ve always done it”. Again, if I had a penny every time I’ve seen THAT thread.
All to avoid the “System.Serializable” attribute? Something that a single thread on Unity forums would lead to “Oh, yeah, just stick this attribute on there and all those problems go away!”
And again, duals up as a lesson that is consistent with how ALL serialization in Unity works.
…
And finally… in the end. They’re going to have to refactor that PlayerPrefs out at some point when they learn the lesson that PlayerPrefs is a BAD place to put save games. So now… they just end up doing twice the work as well as having to unlearn bad habits… might as well just take on that ‘System.Serializable’ hurdle now.
My key point was the scalable part.
And if something goes wrong, they will come here (clearly, since OP already did). And if they used PlayerPrefs the likes of starmanta and I will say “stop using playerprefs, that’s problem number 1”. And if they used json, we’d just say “oh, you forgot the serializable attribute”.
All learning is going to have hurdles.
But why point someone in the direction of hurdles that don’t really benefit them in the long run?