Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Save system

Discussion in 'Scripting' started by Geckoo, Jan 29, 2022.

  1. Geckoo

    Geckoo

    Joined:
    Dec 7, 2014
    Posts:
    112
    Hello everyone. I am searching for a way to create a solid save system for my project. This is not only a way to save a few values, but a full information about game status. In my project, there is only one scene in which progressively some levels are loaded - and others unloaded according to the player's progress in-game. Some objects are available - others not always according to player progress in-game. Is there a way to save in file the full status of a game? PrayerPref is not enough solid. Any idea will be appreciated ++
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    31,217
    Absolutely. But it's a LOT of work. Very few games do it because it's not useful.

    Is there a way to make your car last forever? Yes, but it's a LOT of work.

    Like all engineering problems, one works until one reaches a suitable solution.

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
  3. Geckoo

    Geckoo

    Joined:
    Dec 7, 2014
    Posts:
    112
    Thank you for your reply Kurt-Dekker. I really appreciate it - even if I did not understand the half or your explanation because I have no skill using another save system except PlayerPref. I will take a look at your alternative using JSON. It seems really interesting. I have to think about a better system for my project. Thank you ++
     
    Kurt-Dekker likes this.
  4. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    https://docs.microsoft.com/en-us/dotnet/api/system.io.file.createtext?view=net-6.0

    If you don’t like ones and zeros or player prefs create a text file, and relay important aspects of your game to it lieu. One example I made I was to store the index number of a list of colours, objects and so on; after a key letter or word. And then I would check to see if the text file existed, read the text file, and then grab the colour index of A the object index of A and so on. But you could put anything this, such as a vector 3. Or literally anything at all. You could in fact write your entire game to a text file so that it is entirely modifiable for the player, while all of the game logic is built around and produced after reading this text file.
     
  5. Geckoo

    Geckoo

    Joined:
    Dec 7, 2014
    Posts:
    112
    Thank you Animal Man for the tips. It could be an interesting way to save a game. Actually I am creating my own system, exploiting what you explained to me in this thread. Thank you everyone ++
     
  6. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,173
    If you have no experience, it might be best to first use a prebuilt solution like something from the Asset Store, just to learn those concepts and see the limitations in action. Then you can always write your own.

    At the same time, you can follow a few tutorials. Just a random one that I remember doing: https://catlikecoding.com/unity/tutorials/object-management/persisting-objects/

    But, don't just take any single thing for an answer. Look at multiple approaches, make your own experiments and then apply what you've learned to your own project. Maybe at this point you want to implement a custom save system, maybe you no longer feel the need or found something perfect along the way.

    My dev studio used different practices in the past:

    Key-Value Storage
    Something like PlayerPrefs, but custom. You can build things like GetInt(string key), but also more complex: GetVector, GetArray etc. Everything is stored in dictionaries and serialized to a file. All components go through this global system and save/load their individual state or some managers manage the process for sub-objects. It's all spaghetti and might become too confusing, but it's very flexible. You can turn this system into something designer-driven, e.g. let developers add keys and values directly in the inspector and assign them to prefabs, which on certain actions saves other values (e.g. a quest system or inventory collect thingy). Some topics like versioning the save file can become difficult with this approach, because everything is scattered, or you would have to add additional features from other approaches to make this possible.

    Save Game As Single Source Of Truth
    There's a single class called GameState and it contains everything that needs to be saved, nested in a completely serializable hierarchy. All systems have a reference to this single class or parts of it, but there's only one copy (a singleton potentially). This means, once loaded, all systems simply use this state and write to it, then somebody can save the structure to a file at any point. This system is very easy to understand, because nothing is copied and everyone has the most recent state of things etc. It's also probably one of the fastest approaches performance-wise because there's only a single step involved. However, it tightly couples all components to this big model or parts of it, which reduces flexibility. But things like versioning become easy, because now version 1 is just a single file and model, version 2 a different one and you can add upgraders in between and just pass the new data to the rest of the game.

    Serializer Passed To Subsystems
    Combine both approaches. Somewhere, a serializer loads a file and the reference to this class is passed to all implementers of ISaveable. Each class takes the data it needs or stores it. If its key/value based, the order doesn't matter. If its binary, the ordering is important and probably impossible to implement. Some commercial products work by making the ordering deterministic via GUID keys or manually maintaining an orderer list of saveable components. In any case, you may be able to split the big monolith into multiple parts to make different systems more flexible.

    File Format
    I recommend pretty-printed JSON because its a good balance between performance and editing/debugging. You can even use a ScriptableObject as the data model and then serialize it to JSON. This makes it possible to store save games as assets in the project (maybe as starting points or for cheating/testing). You can see and edit values directly in the inspector. Unity's JsonUtility is extremely fast and supports all types which show in the inspector (but this means its bound to the same limitations, no dictionaries for example). Other serializers handle more complex data types, but are slower, e.g. Json.Net.
    For maximum performance, a custom binary solution is best. Nothing beats a handcrafted serializer which is hardcoded to every property in the game and writes out the minimum number of required bytes to represent the data. However, its also very maintenance heavy. Some libraries build on top of this idea. A search for "serializer" turns up many good results. To name a random one: MessagePack. Keep in mind, you can beat even the best serializer library performance-wise, but probably not in regards to time spent on implementing and maintenance.

    Representing Game State
    The most difficult task: how to represent Unity GameObject state with any system? Everything else is easy: score values, player progress data, inventory, achievements, all can usually be stored as some strings and ints. But scene state (position of GameObjects, active state, animations) are more difficult because the data needs to be converted and the internal Unity state recreated somehow. The most common and practical solution is to build systems for every part of your game. E.g. if the board spawner spawns board game tokens, it can also represent each token position in the save file and load it to spawn it from saved positions). A PlayerController can keep track of its position and rotation and write it to file and load that. You see, it becomes a nightmare for big games, but it works. Some things will give you headaches, like recreating animation state, coroutines, etc. All of these have solutions, but they are involved.
    Some systems attempt to generalize the process. For example, they walk the entire GameObject hierarchy and store the Transform data of each object. Each known component type has some sort of Handler that knows how to serializer/deserializer its state. However, this also breaks down easily in a fight between tradeoffs. If you save everything you waste massive amounts of space and time. But to optimize the system you will need to mark things as saveable or not, or even individual properties, and now you're building an entire editor system to configure the previously hardcoded save/load systems. If anyone (or Unity) introduces new components, you also need to add new specific handlers. This is probably the most complex way of doing things and only yields benefits if both game and team are large.
     
    Last edited: Feb 9, 2022
    Treiger, Magiichan, Canley and 7 others like this.
  7. Geckoo

    Geckoo

    Joined:
    Dec 7, 2014
    Posts:
    112
    @Xarbrough Thank you for this huge explanation. I did not expect anything today, but you made my day. I understand all the alternatives which you propose, but for my project, it seems to me that they are not suitable (at this stage). The main problem with which I am confronting is that I load different scenes according to the player's progress in the game. So I have to save which scenes should be activated, which objects are available or not, player position, etc. It's a nightmare. First, I will try to mix all your alternatives in a single way which I could use in my game. Usually I don't like to buy assets which I could create by myself, but you are right - sometimes, it's a way to manage our time in a better way. I will take a look at some assets so as to understand their pros and cons. Thank you for your help. I really appreciate all your clever explanations. I wish you the best ++
     
    Last edited: Feb 8, 2022
    Xarbrough likes this.
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    31,217
    That was a really good writeup. Appreciate you taking the time. The stuff you write needs to be seen!

    If you don't object (if you do, just pm me), from now on I am going to include a link to your above message when I give out my standard load/save blurb.