Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Yet another savegame aproach

Discussion in 'Scripting' started by LtRegBarclay, Nov 28, 2015.

  1. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9
    Description;

    Hi there. I would like to start another discussion about programming savegames and loading them.
    I have an idea on how to do it and would like to get some feedback about this. Are my ideas a possible way or am I missing a lot of important things? I am very new to Unity, but programming Java for over 15 years now. So my approach is in C#.

    What´s out of scope:

    I already made the save and load dialogues. So UI is not of interest.

    The project:
    The game is a top down dungeon crawler. There are statically placed items to the scene, which can be collected to the players inventory. And there will of course be enemies placed in the scene.

    The idea:
    I dont have to persist the whole scene. It is already persisted and can simply be loaded. All I need to save (as far as i think), is changes to the scene:

    - Which items were collected (destroyed in scene, which leads to adding it to the inventory)
    - Which enemies are already dead (destroyed in scene)
    - What is the players position, health, status
    - What is in the players inventory

    My aproach:
    I am using System.IO.FileStream, BinaryFormatter and serialize/deserialize.
    Every Object in unity has some Unique ID (GetInstanceID())
    So when a save process is started,
    - I search for all Objects with Tag "Item" inside the scene
    - I get their ids, save them comma-separated as string in a SaveGameEntity
    - Same is done with creatures...

    Later when testing the load functionality I tried
    - Deserialize the savegame and get the ids
    - Load the clean level with Application.LoadLevel()
    - Then search for Objects with Tag "Item" again in the new scene
    - All IDs, that are in the scene AND in the savegame, will stay,
    the rest will be destroyed (because these items were already collected,
    or the enemies were already killed)
    - Next change player position, thats it.

    Where I failed so far:
    I tried like in the first line load the level, in the second line search the level for items. This search failed, because the result was null. The level is not yet fully loaded, but the next line in the script was already
    called. It would have to wait until the level was loaded.

    The tipps that I was given:
    There are options like "OnLevelWasLoaded" and "DontDestroyOnLoad". I guess the root problem is, I dont know how to pass informations from one scene to another, because all objects and scenes are destroyed when changing scenes. I tried implementing OnLevelWasLoaded and played with AsynchLevelLoading.

    Yes, I used forum search and Google / My Researches:


    I know there is a Live Training, which is base of my work in persistance:
    http://unity3d.com/es/learn/tutoria...ining-archive/persistence-data-saving-loading

    I know there are asset packs like WhyDoIDoIt
    http://forum.unity3d.com/threads/unity-save-game-level-serialization.138678/

    I would like to be in control and do it myself, if it´s not too much work to do. So I would prefer to have my own solution instead of using some asset pack. I guess I will learn a lot by doing this.

    I don´t wanna use PlayerPrefs.

    I even had some discussions in the #unity3d channel in irc. with first ideas. Thanks to those guys!


    Up to you
    Now I am stranded and not sure wether my idea of only persisting IDs is useful, how to use OnLevelWasLoaded, how to pass informations from scene to scene (without influencing the same scene in a "new game" mode). If anyone could help me out I would love to hear from your experiences and aproaches.

    Thanks for reading.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    I wouldn't do this.

    Instance IDs are guaranteed unique. But they aren't guaranteed to be the same every time you run the game.
     
  3. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9
    ok which would lead to the whole process failing :( any other ideas? what is the theory of saving a unit scene? what should be saved? is it always loading via Application.LoadLevel, then apply the changes in the savegame? or a whole different approach?...
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    What should be saved is any differencing information.

    You have the default state (no alterations, freshly loaded scene). What needs to be removed (if anything), and what needs to be added.

    Things that can be removed probably should be flagged in some way as removable. As well as a unique identifier to identity it. A persistent one. This could be easily done with a script with a string 'id' value that you attach a GUID to...

    Code (csharp):
    1.  
    2. public class RemovableItem : MonoBehaviour
    3. {
    4.  
    5.     public string Uid = System.Guid.NewGuid().ToString();
    6.  
    7. }
    8.  
    Now you could search through the scene for this script and delete it if the guid is saved.

    So in saving, you just inspect the scene... see what removables exist, and compare that to the list of all possible removables on a clean load. And save the uid's of the items that are no longer in the scene.



    Anything that needs to be added would have to be done in another manner. Firstly you'll probably need them to be prefabs, and you'll need a way to identify the prefabs, and a way to load them (Resources could be used). Then you could have a script on those like this:

    Code (csharp):
    1.  
    2. public class AddableItem : MonoBehaviour
    3. {
    4.  
    5.     public string ResourceId;
    6.  
    7. }
    8.  
    Then you could just search the scene for all addable items. Save its resource id. And anything about its state you want.

    And when you load, you just load those resources by the id, and modify its state (position, rotation, etc), and place in the scene.
     
    Nigey likes this.
  5. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9
    Thanks for your fast response. I guess I wont have something to be added, just removed.

    If I understand you right, your aproach is the same. Just without using GetInstanceID and instead using my own UUIDs. I guess I could do this.

    BUT: The problem I described above would still exist:

     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Some general ramblings on the theory of saving games in general. Hope it helps.

    There are several steps in the saving process. Breaking it down into steps can make is easier to conceptualize.
    1. Obtain the data to be saved
    2. Serialize the data
    3. Write the data to storage
    Loading is the opposite
    1. Read the data from storage
    2. Deserialize the data
    3. Apply the data to the game state
    Each part of the process should be treated independently. At the moment you seem to have them all jumbled up together.

    Writing and reading data from storage is trivial. You can use PlayerPrefs or System.IO or WWW. It really doesn't matter, and is not part of your problem here. Just look up how to do it. Its all standard stuff.

    Serializing and deserializing is a little more difficult. But its still fairly standard stuff you can grab from a library. Some common formats for serializing data include JSON, XML, binary formatter, images and so on.

    The critical step that is very game dependent is the obtaining of data and applying the data to the game. There are two major approaches to this:
    1. Save all game state
    2. Save only the game state that has changed from initial conditions
    I personally prefer to save the entire game state. This leads to several simplifications
    • There is no need to check if game state has changed
    • Game state can be saved in the same format as the initial game data
    • Game state can be applied to the game through the same mechanisms used to set up the game state on a new game
    With this method you can treat starting a new game as if you are simply loading a saved game.Of course the downsides of this method is that saved games might be larger. And it can also require a little bit of stepping out of Unity's scene work flow.
     
    LtRegBarclay likes this.
  7. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9

    Thank you! That is really what I am thinking about. Serialization is not the problem. I already persisted files and could see them in the loading screen and deserialize them. So the main toppic is really as you mentioned, how to apply changes or load the full take of the savegame as you mentioned.

    I dont prefer any of both methods (changes or full saving). I am free to try both. But i am not sure how. I am able to do the serialization.... Lets focus on this instead of serialization and the UI dialoges. Do you have suggestions there?

    Best regards
     
  8. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9
    Ok I am spinnig in circles now. I tried using "DontDestroyOnLoad". But that class only takes objects of type UnityEngine.Object. But when I change my class to this, I cant serialize it anymore.... ******
     
  9. LtRegBarclay

    LtRegBarclay

    Joined:
    Aug 26, 2015
    Posts:
    9
    Is nobody here using savegames??
     
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Actually no, I don't bother to save games. My games are currently very short lived. Saving makes no sense. ;)
     
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Our approach to persistent destruction of objects in a scene is inverted from yours. Each static object in the scene checks it's state on startup. Copy-pasted:

    Code (csharp):
    1. private void Start() {
    2.     if (SaveSystem.CheckTrigger(mySaveTrigger))
    3.         Destroy(gameObject);
    4. }
    This only works if your objects are remove-only, if you can add arbitrary objects to the scene, this approach would be a terrible one.

    What object is this? The save game wrapper?
     
    Kiwasi likes this.
  12. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    @LtRegBarclay - I think you're looking for the order in which to load and save so that everything is initialized properly.

    Use a coroutine to give the scene objects one frame to finish their Start methods.

    Also, searching is slow. One solution is to put a singleton instance of a MonoBehaviour (e.g., "SaveSystem") in the scene and have the scene objects register with that singleton. I used a MonoBehaviour below so it can receive OnLevelWasLoaded messages:

    Code (csharp):
    1. public class SaveSystem : MonoBehaviour {
    2.  
    3.     public SaveSystem Instance { get; private set; }
    4.     public event System.Action SaveState = new delegate {};
    5.     public event System.Action LoadState = new delegate {};
    6.  
    7.     void Awake() {
    8.         Instance = this;
    9.     }
    10.  
    11.     IEnumerator OnLevelWasLoaded(int level) {
    12.         yield return null; // Wait 1 frame to allow all scene objects to initialize.
    13.         LoadState();
    14.     }
    15.  
    16.     public void SaveData(string guid, object data) {
    17.         // Your code to serialize data under the key 'guid'.
    18.     }
    19.  
    20.     public object LoadData(string guid) {
    21.         // Your code to look up the data under the key 'guid' and return it.
    22.     }
    23. }
    Then add a "saveable" MonoBehaviour to scene objects that need to save and load state data. It'll register with the SaveSystem for save and load events. The SaveSystem code above automatically sends the LoadState event after loading a level. Before changing levels, you should call SaveSystem.Instance.SaveState() to tell the scene objects to save their state first.

    Here's an example of an abstract base Saveable class:

    Code (csharp):
    1. public abstract class Saveable : MonoBehaviour {
    2.  
    3.     public string guid = System.Guid.NewGuid().ToString();
    4.  
    5.     void Start() { // register.
    6.         SaveSystem.Instance.SaveState += OnSaveState;
    7.         SaveSystem.Instance.LoadState += OnLoadState;
    8.     }
    9.  
    10.     void OnDestroy() { // unregister.
    11.         SaveSystem.Instance.SaveState -= OnSaveState;
    12.         SaveSystem.Instance.LoadState -= OnLoadState;
    13.     }
    14.  
    15.     protected abstract void OnSaveState();
    16.     protected abstract void OnLoadState();
    17. }
    You can then create subclasses to save and load different state data, such as whether the object exists or not, its position, its progress in the dialogue, or even if it should spawn new objects. Here's a hand-wavy example of saving and loading a scene object's position:

    Code (csharp):
    1. public class SaveablePosition : Saveable {
    2.  
    3.     protected override void OnSaveState() {
    4.         SaveSystem.Instance.SaveData(guid, transform.position);
    5.     }
    6.  
    7.     protected override void OnLoadState() {
    8.         transform.position = SaveSystem.Instance.LoadData(guid);
    9.     }
    10. }
     
    Kiwasi and Dustin-Horne like this.