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.
  2. Dismiss Notice

Question Real save\load system, how?

Discussion in 'Scripting' started by babaramanamarama, Nov 6, 2022.

  1. babaramanamarama

    babaramanamarama

    Joined:
    Oct 4, 2020
    Posts:
    34
    Hi all,
    First of all I know how to save or load data and I also know how to keep data when you load another scene. But all this is not enough when you are doing real complex game. I want my game to have different locations and player should be able to revisit locations. Of course, there are different objects in every locations, for example - containers. And I want for example to save/load state of container #6 in scene #3, while I have currently loaded scene #5. Or even more - to save states of all containers in level #6 to #10, which player never even visited before. I've checked tutorials, even marketplace, but everybody only speak about most stupid and basic save\load features. Any idea how to do it?
     
    Last edited: Nov 6, 2022
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Whether you are saving one boolean value (such as "Sauron was defeated!") or you are saving the complex state required for a Skyrim clone with a Minecraft worldmap and Call-of-Duty unlock and prestige system, it's always the same:

    - make data structures to store the necessary state
    - make identifiers to map this state to and from the scenes / assets your game
    - serialize and save the state
    - load and deserialize the state
    - restore it all as necessary when the player is playing

    It is all exactly the same steps.

    Complexity of your game does not change the quality of the above steps, just the quantity of steps.

    You sound like you've already seen this, but I'll add it anyway.

    Load/Save steps:

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

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    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
     
    NicBischoff likes this.
  3. babaramanamarama

    babaramanamarama

    Joined:
    Oct 4, 2020
    Posts:
    34
    Kurt-Dekker,
    Thanks for detailed info, But that does not answer my question. Serializing json or other format or defining what objects to serialize, while tedious and boring actually is not too hard. My question is not about saving some integer or structure or something to disk or player prefs, but about saving savegame. When player starts game he can not know how many levels there are, how many containers and interactable objects there are, but game must know it exactly and every copy of savegame should contain information about ALL those objects. So I have to save not a single level, but all of them at once and hopefully do it most easy way. I checked some documentation and searched marketplace, but there is no plugin that can do it. Another hard thing is one should populate all those containers at development time and store information somewhere. The only thing I can imagine now is using of own editor and databases, which I would not like to do - it would be terrible and hard.
    I want to use Unity for this project, because it will be a mobile game with stylized graphics. I think Unity is better for stylized mobile games, due to smaller build sizes, but more I think about it more I lean towards UE.
     
  4. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,887
    There is no difference. Your save game will be a bunch of numbers and strings. Open up a .prefab or .unity scene file in notepad and you will see. They're written in YAML, which is similar to JSON but with simpler syntax.

    Your save can be a JSON file that contains all the data required to restore the game back to where it is.

    I would also look into Database normalization to see how you can break your data down into simpler forms to be stored in a database.

    Code (csharp):
    1. public class GameState
    2. {
    3.     public Dictionary<int, (string SceneName, float PositionX, float PositionY, float PositionZ)> AllContainersLocationData;
    4.     public List<(int ContainerID, string ItemID, int Amount)> AllContainersContentsData;
    5. }
    All the data will be there. We can look at container with ID 5.

    What scene is it in?
    Code (csharp):
    1. var container = AllContainersLocationData[5];
    2.  
    3. Debug.Log($"{container.SceneName}");
    What items are inside it?
    Code (csharp):
    1. var filteredContents = AllContainersContentsData.Where(i => i.ContainerID == 5);
    2.  
    3. foreach (var (ContainerID, ItemID, Amount) in filteredContents)
    4. {
    5.     Debug.Log(ItemID);
    6. }
     
    Last edited: Nov 6, 2022
  5. babaramanamarama

    babaramanamarama

    Joined:
    Oct 4, 2020
    Posts:
    34
    Why can not you understand? When you saving game you only have current scene. You can check all objects in current scene and save. You can not check state of object that is in ANOTHER scene! That is why you can not use same approach. You have to save everything - every object state. It means you have to keep all this absolutely separated from game objects. You have every time you load level check database, find that level objects and load them, then if you move to another scene you have to remember DIFFERENCE between savegame and current state. This is terrible way doing things. In UE there is much easier approach.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    WHAT?! That's absolute MADNESS! In no way shape or form should you EVER write a save system like that.

    If you did, how would your system work if there was NO save data, or if you wanted to add more levels in the future?!

    Always structure your save data so that there is some collection of save data. This way "no data for level X" means "level X plays precisely the way it would on a first playthrough."

    Why would you need to? Your save data actually TELLS you what the state is in another scene.

    If you read my link above, you would see that upon loading a scene, the FIRST THING you do is iterate your save data and ask "Has the user been here? Do I need to use this save data to change the state of anything in the scene?" eg, like doors open, chests open, monsters dead, etc.

    ... which is trivial. Instead of having a bool inside of a door script that says "This door is open," instead map that door to directly access the save data, and if there is no data to revert to its default state (locked or open, as your game design dictates).

    Here is an ULTRA trivial way to hook new GameObjects up to save data. I call it a conduit:

    Code (csharp):
    1. // just a simple bidirectional link object to
    2. // bidirectionally bridge data with presentation.
    3.  
    4. public class IntegerConduit
    5. {
    6.     public readonly System.Action<int> Set;
    7.     public readonly System.Func<int> Get;
    8.  
    9.     public IntegerConduit(System.Func<int> Get, System.Action<int> Set)
    10.     {
    11.         this.Set = Set;
    12.         this.Get = Get;
    13.     }
    14. }
    When the door appears, it constructs a unique key saying "I am door X in scene Y" (both of which it can trivially find out at runtime, requiring you to do zero work authoring, apart from giving doors unique names, which is probably smart anyway), and then it contacts the save system and says "Here is my key, I need to know my state... give me a conduit to a boolean (or integer or whatever)!"

    The save system gives it back a conduit object above that maps directly into the save state.

    If the save system HAS seen the level, it would map to existing variables.

    If the save system HAS NOT seen the door, it would create a new key in its database and map the conduit to that.

    Any time the door changes its state, it calls the above conduit to change the data, which ALWAYS lives in the save system.

    It's so trivial that once you start using the above pattern, you'll wonder how you ever made local variables such as
    bool open
    when the above is so simple.

    If none of that works, go get something like EasySave3 in the asset store: Disclaimer: I have never used ES3.
     
    angrypenguin likes this.
  7. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,135
    UE's approach is identical to their explanations: you create a class that contains entries for everything that you want saved, you feed the information into an instance of the class, and then you ask that it be saved to a "slot".

    https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/SaveGame/
     
    Last edited: Nov 6, 2022
    Kurt-Dekker likes this.
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    And honestly I can't really think of any other "general" way to save!

    @OP: the problem doesn't change just because it's Unity or because it's Unreal or because it's your own engine:

    State needs to be saved. State needs to be loaded.
    In-game objects have to be mutated based on the loaded data.
    Player manipulation of in-game objects may need to mutate save data
     
  9. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,135
    If I had to guess what he's trying and failing to explain is a save system similar to Bethesda where the world state is stored in one or more databases (eg one or more "master" databases, one or more "expansion" databases, and one or more "patch" databases). Saves are made by recording all changes to the world in a "save" database.
     
  10. babaramanamarama

    babaramanamarama

    Joined:
    Oct 4, 2020
    Posts:
    34
    Kurt-Dekker,
    Interesting info, thanks again, but I can not use that approach, because I need to know what I am saving! If I implement it on object level, when every object saves itself, then I will need manually check all levels and objects and what components they have. That's a nightmare. And while it may be ok for tens of objects it easily turns into a nightmare for hundreds and thousands. That's why I want to have one "place" from where I save everything and which shows me every object in level or game that will be saved. I checked unity marketplace and while there are good save systems for small games, they are not suitable for even medium ones, because they require you to select scene object, add save component and then check whatever you want to save - position, rotation, etc. At first it seems good solution, but again it brings everything down to single object level, while I want centralized system.
     
  11. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    To make a save and load system you will need to build infrastructure to manage your prefab or object ID’s or the assembly of those objects in script. If you have a list of prefabs then you can save the index of those prefabs and restore them to their containers by learning how to create a file and save a number. Then you will have a file for each container, and that file will reference your object infrastructure providing it with the index to know what it is suppose for it to be restored; and so you can reassemble the contents of the container and save after its modification.

    Easy way to save number and restore a number - https://docs.unity3d.com/ScriptReference/PlayerPrefs.SetFloat.html

    https://docs.unity3d.com/ScriptReference/PlayerPrefs.SetInt.html

    so if your container had 10 slots, you can ask what is the index number of
    PlayerPrefs.GetInt(“CONTAINER01_SLOT_01”);
    And use set int to create it and overwrite it. And this index talks about a the prefab list or whatever infrastructure you designed to structure your objects
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Bethesda saves are also notoriously bloated, slow, and prone to corruption.

    OP, unfortunately there's no magic one way to save everything, especially because of Unity's half C#/half C++ approach, and I guess, Unity's blank slate approach too. Tutorials are always just going to explain the basics, because the expectation is that once you know the basics you can expand on that with your own agency.

    Asset store solutions are always going to be very 'general' because, honestly, every game is different and needs a slightly unique save game solution.

    If you want a Bethesda style save system, you're going to have to architect it. If you want dynamic properties being saved, you'll have to manage the serialisation of polymorphism.

    Though I've always had good success just using a net of scriptable objects to maintain the data for save games. So long as the SO at the top is always referenced (which is trivial to do), the data in all the other SO's will persist down the web. This works too when certain scenes aren't loaded, as SO's live independent to them. But again, it depends on the game.
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    If you have hundreds of thousands of objects that all have save state... what OTHER method besides the above are you contemplating? It's not like you can get out of doing the work. You can certainly automate it with editor tooling, but long before you automate it you actually have to make it work at laboratory scale, eg, onesies and twosies, so you can prove to yourself you did it correctly.

    As I noted above, each object could know exactly who it is (as I suggested by scene name concatenated with GameObject name) and then each object just queries the datastore and says "have you seen me before? If so, i need to know my state, if not, I need to be given access to state that I can change."
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    Is it really that bad? I would dearly hope that you're already making use of Unity's Prefab functionality for most objects in your game, so all you need to do for the vast majority is add a component to a prefab.

    Personally, my saved objects all have a unique ID which is independent of their scene / hierarchy path / etc. This involves the slightly painful step of assigning those IDs, which in turn brings me to... automate everything! Well, lots of things. :)

    My savable objects generate themselves an ID if they don't already have one and are not a prefab, so I almost never have to do that manually. If that wasn't in place at the start of content development I'd also write an Editor function which does it for every savable object in the scene when a button is pressed. And, importantly, I also have a test tool which checks for missing and/or duplicate IDs across the entire project and gives me a nice list, so no human has to stress over tracking that stuff.

    My two rules of thumb for working with large amounts of data which can break your game are:
    1. Make the computer do as much of the work as you can.
    2. Mistakes happen. They're no big deal as long as you're prepared to a) find them and b) fix them.
     
    bunnybreaker and spiney199 like this.
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    Please don't do this to yourself.

    It's in the name. PlayerPrefs exist to store and load player preferences. Volume = 3. Difficulty = Normal. That kind of thing. And that is all. Last time I checked it was incredibly short of what you'd need to build an effective saved game system around, and it stored stuff in locations inappropriate for large amounts of data (the Windows Registry, as one example) and reading/writing was painfully slow on some platforms.

    For anyone who stumbles on this in the future, for any non-trivial data storage your life will be far easier if you look into a couple of things:
    1. File IO in C#. Search for "C# read and write text files" and you'll get loads of tutorials.
    2. JSON, XML, or other serialization libraries for C#. Pass them a set of serializable objects and they'll give you a blob of text to write to a file, and vice versa.

    You'll have to design your serializable objects, but you were going to have to do that anyway.
     
  16. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    I'd also like to recommend the Odin Serialiser: https://github.com/TeamSirenix/odin-serializer

    Namely for its external reference resolver feature: https://odininspector.com/tutorials/serialize-anything/external-reference-resolver

    In the context of Unity, being able to pass it a lump of data which includes UnityEngine.Object references and have it just swap them out with something usable - such as a unique ID - and use these ID's on load to look up from a database, is a real complexity and time saver (database system to look up these ID's with notwithstanding).
     
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    Personally I've been using FullSerializer. It's great to work with and MIT licensed. Unfortunately it's no longer maintained.
     
  18. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
  19. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Isn't this just a normal save system with more steps?
     
  20. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    I just looked at it, it's kinda awful code actually. It's not a Dict<T,T> as you might expect... it's a linear search. :)

    I forked it after a quick test a year or so ago... it's only value is if you are used to
    PlayerPrefs.***()
    -type coding, it lets you do it almost same-same.

    The point is it gets pasted into one giant JSON blob in a file. That makes it easy to have player slots or save slots.
     
  21. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,883
    Fair, as a less awful player-prefs drop in I guess it works.

    As a stand in for a proper save system, not so much.
     
    Kurt-Dekker likes this.
  22. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,887
    Why is it terrible? Isn't it better to separate data from objects/visuals? Skyrim is just a database. You walk into a Cell/Region and it loads the objects from that table in the database.

    How can Unreal do it any differently? https://forums.unrealengine.com/t/h...bjects-between-different-levels-help/647864/4
     
  23. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    486
    For our network game we solved initial state for new players with a interface each networked object that needed sync implemented this interface and when a new player connected the server sends the current state. Each networked object was responsible for packing the data and unpack it on the client. Save / load system works the same basicly
     
    Kurt-Dekker likes this.