Itâs one of those situations where you can do things one of many many different ways, and which way you choose is really just a matter of preference and whatever seems easiest for your setup.
PlayerPrefs saves to the registry on Windows builds, and to caches on Android and iOS. Itâs good for preference data that you want to save but isnât a big deal if itâs lost. Volume settings, screen resolution, text scales- most of the things you normally see in âOptionsâ menus can be handled this way. If a value is lost, your code should just use the default for that option and make the user change it again (if they like), so the fact that the data is so fragile shouldnât hurt you as long as you donât use it for anything really important.
Aside from that, youâll need to serialize and save / deserialize and load the data yourself. Lots of options here. As mentioned, you can have different classes handling this independently, or do it in one big object all at once.
In the former case, youâll end up with a dozen little save files on the disk simultaneously (the story progress save file, the character data save file, the achievement progress save file, etcâŚ). This is a pain in the ass to keep track of, and easy to cheat with- imagine duplicating the file with your money, buying a ton of stuff until youâre broke, then replacing the âbrokeâ file with the earlier copy and getting your money back- and your items are still there because theyâre a different file. This is only one of the numerous ways in which this can be abused, and more importantly having the save operations so spread out just over-complicates the project.
In the latter case, you pick and choose which data you want from each object and collect it into one big âsave dataâ serializable object that has everything you need to load the game with. This can be managed in a manner similar to PlayerPrefs (everytime your characterâs level increases, store the new value to the central SaveData object, in addition to the characterâs status script). Even better, you can have an event called PreparingToSave or something that every object with savable data signs up for when the game starts. When the event fires, all objects that are listening for it are notified, and will copy over whatever data they have that they want to keep to the central SaveData object. In the next frame, the SaveData object is serialized, and saved to disk, and the reverse of this process occurs when loading.
This is more realistic, and it means you only need to handle the actual saving and loading in one place.
In either case, youâll also have to decide how you want the data serialized- BinaryFormatter comes with .NET and works fine, but itâs a bit slow and pretty limited. It does binary serialization, which means the data it outputs is all 1s and 0s and just gibberish without knowing how to deserialize it. JsonUtility, on the other hand, comes with Unity and allows you to turn a serializable object into a string. This can be especially useful for transmitting save data over the internet, but it works for local files too if you like having a bit of readability (unless you encrypt it, you can just open it up in a text editor and read it and it makes something resembling sense).
Other alternatives include XML, ZeroFormatter, MessagePack⌠It just depends on the balance of features, readability, security, speed, and filesize that you need.