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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Please help me serialize gameobjects ( Don't advertise assets )

Discussion in 'Scripting' started by Lethn, Jun 5, 2016.

  1. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Well, I am not completely sure as to where you are running into issues, but I edited the code to work with changing scenes.
    However, the problem I ran into is, what happens to the objects that were in the scene to begin with? What I think we need to do is give every saveable object a unique ID, and then when we save, we save the ID with it, and when we load, we search the scene for the object with that ID and destroy before we spawn in the new object.
    The problem with that is, I dont know of a good way to generate a unique ID. An operation like this needs to be taken seriously since any change in a objects ID will ruin every single game save.
    Currently I have a script that is trying to set the ID when the editor detects any hierarchyWindowChange, which might be enough (and is probably also very poor for performance depending on how many objects you have), except then I ran into the issue of how to handle generating a unique ID when we duplicate an object in the editor (You can see me talk about it in the SaveableGameObjectAutoSetup class below). That object would have duplicated the ID... I did not think of an answer to this, so your going to have to do that =).

    There are also many other issues to take into account, such as what happens if we want to have our player enter a new area, which would send them to a new scene, but in reality that new scene is connected to the other scene. Which means when they walk back through that portal, we would of had to save that scene in a temporary place so that they can load it up, and then there is the issue of how do we save which side of the portal the player was on last, etc...


    Here are the classes that I had to change/add. Hopefully I made it clear as to what was changed/added.
    You can also just download the project I attach.

    First, here are some completely new scripts...
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System;
    5. using System.Collections.Generic;
    6.  
    7. //********Must be put inside a folder named Editor
    8.  
    9. [InitializeOnLoad]
    10. public class SaveableGameObjectAutoSetup
    11. {
    12.     static SaveableGameObjectAutoSetup()
    13.     {
    14.         EditorApplication.hierarchyWindowChanged -= SetupSaveableGameObjects;
    15.         EditorApplication.hierarchyWindowChanged += SetupSaveableGameObjects;
    16.     }
    17.     static void SetupSaveableGameObjects()
    18.     {
    19.         SaveableGameObject[] saveables = GameObject.FindObjectsOfType<SaveableGameObject>();
    20.  
    21.         if(saveables.Length > 0)
    22.         {
    23.             UnityEngine.Object saveablePrefab = PrefabUtility.GetPrefabParent(saveables[0].gameObject);
    24.  
    25.             if(saveablePrefab != null) //If we didnt create a prefab yet, then do not set the uniqueID so that the prefab will always have a empty value.
    26.             {
    27.                 for(int i = 0; i < saveables.Length; i++)
    28.                 {
    29.                     SaveableGameObject saveable = saveables[i];
    30.  
    31.                     if(saveablePrefab != saveable.gameObject)
    32.                     {
    33.                         saveable.originalPrefabNameReference = saveablePrefab.name;
    34.  
    35. //Problem - If we duplicate an object in the editor, the id will be duplicated too... how do we handle this?
    36.                         if(saveable.uniqueID == string.Empty)
    37.                         {
    38.                             saveable.uniqueID = Guid.NewGuid().ToString();
    39.                         }
    40.  
    41.                         EditorUtility.SetDirty(saveable);
    42.                     }
    43.                 }
    44.             }
    45.         }
    46.     }
    47. }
    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. //Taken from http://answers.unity3d.com/questions/489942/how-to-make-a-readonly-property-in-inspector.html
    6.  
    7. public class ReadOnlyAttribute : PropertyAttribute {}
    8. [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
    9. public class ReadOnlyDrawer : PropertyDrawer
    10. {
    11.      public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    12.      {
    13.          return EditorGUI.GetPropertyHeight(property, label, true);
    14.      }
    15.      public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    16.      {
    17.          GUI.enabled = false;
    18.          EditorGUI.PropertyField(position, property, label, true);
    19.          GUI.enabled = true;
    20.      }
    21. }

    Here are the edited scripts...
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4.  
    5. [Serializable]
    6. public class GameSaveData
    7. {
    8.     public string saveName;
    9. /****Added****/    public string sceneName;
    10.     public List<SaveData> saveDatas;
    11.  
    12.     public GameSaveData(){}
    13.     public GameSaveData(string saveName, string sceneName, List<SaveData> saveDatas)
    14.     {
    15.         this.saveName = saveName;
    16. /****Added****/    this.sceneName = sceneName;
    17.         this.saveDatas = saveDatas;
    18.     }
    19. }
    Code (CSharp):
    1.  
    2. using System;
    3.  
    4. [Serializable]
    5. public struct SaveData
    6. {
    7. /****Added****/    public string uniqueID;
    8.     public string resourceName;
    9.     public string xmlData;
    10.  
    11.     public SaveData(string uniqueID, string resourceName, string xmlData)
    12.     {
    13. /****Added****/    this.uniqueID = uniqueID;
    14.         this.resourceName = resourceName;
    15.         this.xmlData = xmlData;
    16.     }
    17. }
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [DisallowMultipleComponent]
    5. public abstract class SaveableGameObject : MonoBehaviour, ISaveable
    6. {
    7.     //We are going to need the original prefab name to Load it in later.
    8. /*******Added Readonly******/
    9.     [ReadOnly]
    10.     public string originalPrefabNameReference;
    11.  
    12. /*******AddedStart******/
    13.     //Ideally you only want to be able to access the value, not set it, but I am just doing it like this cuz it makes editor code easier.
    14.     [ReadOnly]
    15.     public string uniqueID = string.Empty;
    16. /*******AddedEnd******/
    17.  
    18.     void Awake()
    19.     {
    20.         //We can only have 1 ISaveable per gameobject.
    21.         //We could probably setup a unique id system for each gameobject and their ISaveables so that we can have multiple ISaveables, kinda like
    22.         //how the unity networking is setup, but for simplicity we will keep it to one per object.
    23.         if(GetComponents<ISaveable>().Length > 1) GameObject.Destroy(this);
    24.  
    25.         //We could avoid this by when pressing save we check all gameobjects and their components to see if they implement ISaveable.
    26.         //However, that could cause poor performance depending on how many gameobjects you have in the scene.
    27.         //So instead I will just subscribe this object to the savemanager.
    28.         SaveManager.saveables.Add(this);
    29.     }
    30.  
    31.     void OnDestroy()
    32.     {
    33.         SaveManager.saveables.Remove(this);
    34.     }
    35.  
    36. /*******Changed******/
    37.     public SaveData Save()
    38.     {
    39.         return new SaveData(uniqueID, originalPrefabNameReference, SaveMe());
    40.     }
    41.  
    42.     public void Load(string xmlData)
    43.     {
    44.         LoadMe(xmlData);
    45.     }
    46.  
    47.     public abstract string SaveMe();
    48.     public abstract void LoadMe(string xmlData);
    49. }
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4. using System.Collections.Generic;
    5. using System.IO;
    6. using UnityEngine.SceneManagement;
    7.  
    8. public class SaveManager
    9. {
    10.     public static string folderPath = IOHelper.GetOrCreateFolderInMyDocumentsPath("MyGameSaveDatas");
    11.     public static string filePath = Path.Combine(folderPath, "GameSaves.MyGame");
    12.  
    13.     public static HashSet<ISaveable> saveables = new HashSet<ISaveable>();
    14.     static List<GameSaveData> gameSaves; //Instead of a separate file per game save, they will all be in one file.
    15.  
    16. //*******Changed currentGameSave******
    17.     public static GameSaveData currentGameSave {get {return (gameSaves.Count == 0 || gameSaves == null) ? null : gameSaves[0];}}
    18.  
    19. //******AddedStart*******
    20.     //A hackish way to assign on startup, could also just make this class a singleton.
    21.     static bool assignToScene = AssignToScene();
    22.     static bool AssignToScene()
    23.     {
    24.         SceneManager.sceneUnloaded += OnSceneWasUnloaded;
    25.         if(assignToScene){} //This is to get unity to stop telling us the variable wasnt used.
    26.         return true;
    27.     }
    28.     static void OnSceneWasUnloaded(Scene scene)
    29.     {
    30.         saveables.Clear();
    31.         gameSaves.Clear();
    32.     }
    33. //******AddedEnd*******
    34.  
    35.     public static void SaveGame(GameSaveData saveData)
    36.     {
    37.         //We need to make sure we load in all our previous saves since we are saving them all in the same file, otherwise the new save will overwrite all old saves.
    38.         if(gameSaves == null) LoadGameSaves();
    39.  
    40.         if(saveData != null)
    41.         {
    42.             if(!gameSaves.Contains(saveData))
    43.             {
    44.                 gameSaves.Add(saveData);
    45.             }
    46.  
    47.             SetGameSaveToFirstInList(saveData);
    48.         }
    49.  
    50.         XMLSerialization.ToXMLFile(gameSaves, filePath);
    51.     }
    52.  
    53.     public static void LoadGame(GameSaveData data)
    54.     {
    55.         if(data != null)
    56.         {
    57.             //Since we are going to load a new game, we need to clear the saveables since every object will be recreated.
    58.             saveables.Clear();
    59.  
    60.             SetGameSaveToFirstInList(data);
    61.  
    62. /*******Added******/ SaveableGameObject[] alreadySpawnedSaveableObjects = GameObject.FindObjectsOfType<SaveableGameObject>(); //We do this here so we dont keep calling it within the loop for performance reasons.
    63.  
    64.             //Now we load in the objects.
    65.             for(int i = 0; i < data.saveDatas.Count; i++)
    66.             {
    67. /*******Added******/ DestroyDuplicateObjects(data.saveDatas[i].uniqueID, alreadySpawnedSaveableObjects);
    68.  
    69.                 ISaveable obj = ((GameObject)GameObject.Instantiate(Resources.Load(data.saveDatas[i].resourceName))).GetComponent<ISaveable>();
    70.                 obj.Load(data.saveDatas[i].xmlData);
    71.             }
    72.         }
    73.     }
    74.  
    75. /*******Added******/
    76.     static void DestroyDuplicateObjects(string uniqueID, SaveableGameObject[] alreadySpawnedSaveableObjects)
    77.     {
    78.         for(int i = 0; i < alreadySpawnedSaveableObjects.Length; i++)
    79.         {
    80.             if(alreadySpawnedSaveableObjects[i].uniqueID != string.Empty && alreadySpawnedSaveableObjects[i].uniqueID == uniqueID)
    81.             {
    82.                 GameObject.Destroy(alreadySpawnedSaveableObjects[i].gameObject);
    83.                 return;
    84.             }
    85.         }
    86.     }
    87.  
    88.     public static List<GameSaveData> LoadGameSaves()
    89.     {
    90.         if(!File.Exists(filePath))
    91.         {
    92.             gameSaves = new List<GameSaveData>();
    93.         }else{
    94.             gameSaves = XMLSerialization.FileToObject<List<GameSaveData>>(filePath);
    95.         }
    96.  
    97.         return gameSaves;
    98.     }
    99.  
    100.     //We keep our current save at the front of the list to keep track of it. Another way I guess would be to add a unique identifier to each save data.
    101.     static void SetGameSaveToFirstInList(GameSaveData data)
    102.     {
    103.         int itemIndex = gameSaves.IndexOf(data);
    104.         if(itemIndex > 0)
    105.         {
    106.             gameSaves.RemoveAt(itemIndex);
    107.             gameSaves.Insert(0, data);
    108.         }
    109.     }
    110.  
    111.     public static void DeleteAllGameSaves()
    112.     {
    113.         gameSaves.Clear();
    114.         SaveGame(null);
    115.     }
    116.  
    117.     public static void DeleteGameSave(GameSaveData data)
    118.     {
    119.         gameSaves.Remove(data);
    120.         SaveGame(null);
    121.     }
    122. }
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using System.Collections.Generic;
    6. using UnityEngine.EventSystems;
    7. using UnityEngine.SceneManagement;
    8.  
    9. public class SaveUI : MonoBehaviour
    10. {
    11.     public Button newSaveButton;
    12.     public InputField newSaveNameInputField;
    13.     public Button saveButton;
    14.     public Button showSavesButton;
    15.     public Button deleteAllButton;
    16.     public Button saveSelectedButton;
    17.     public Button loadSelectedButton;
    18.     public Button deleteSelectedButton;
    19.     public RectTransform gameSavesDisplayContainer;
    20.     public Button gameSavesDisplayButtonPrefab;
    21.  
    22.     Dictionary<Button, GameSaveData> gameSavesDisplayButtons = new Dictionary<Button, GameSaveData>();
    23.     Button currentSelectedButton;
    24.     static GameSaveData loadThisDataAfterSceneReset;
    25.  
    26.     void Awake()
    27.     {
    28.         newSaveButton.onClick.AddListener(NewSaveGame);
    29.         saveButton.onClick.AddListener(SaveGame);
    30.         deleteAllButton.onClick.AddListener(DeleteAllSavedGames);
    31.         showSavesButton.onClick.AddListener(ShowGameSaves);
    32.         saveSelectedButton.onClick.AddListener(SaveSelectedGame);
    33.         loadSelectedButton.onClick.AddListener(LoadSelectedGame);
    34.         deleteSelectedButton.onClick.AddListener(DeleteSelectedGame);
    35.    
    36. /*******Added******/ SceneManager.sceneLoaded += OnSceneWasLoaded;
    37.     }
    38.  
    39. //******Added OnDestroy*****
    40.     void OnDestroy()
    41.     {
    42.         SceneManager.sceneLoaded -= OnSceneWasLoaded;
    43.     }
    44.  
    45. //*******Changed****** was originally the deprecated OnLevelWasLoaded
    46.     void OnSceneWasLoaded(Scene scene, LoadSceneMode mode)
    47.     {
    48.         if(loadThisDataAfterSceneReset != null)
    49.         {
    50.             SaveManager.LoadGame(loadThisDataAfterSceneReset);
    51.             loadThisDataAfterSceneReset = null;
    52.             ShowGameSaves();
    53.         }
    54.     }
    55.  
    56.     void NewSaveGame()
    57.     {
    58.         GameSaveData saveData = new GameSaveData();
    59. /*******Added******/ saveData.sceneName = SceneManager.GetActiveScene().name;
    60.         saveData.saveName = newSaveNameInputField.text;
    61.         newSaveNameInputField.text = "";
    62.         saveData.saveDatas = GetSaveDatas();
    63.         SaveManager.SaveGame(saveData);
    64.         ShowGameSaves();
    65.     }
    66.  
    67.     void SaveGame()
    68.     {
    69. /*******Added if check******/
    70.         if(SaveManager.currentGameSave != null)
    71.         {
    72.             SaveManager.currentGameSave.saveDatas = GetSaveDatas();
    73.             SaveManager.SaveGame(SaveManager.currentGameSave);
    74.         }
    75.     }
    76.  
    77.     List<SaveData> GetSaveDatas()
    78.     {
    79.         List<SaveData> saveDatas = new List<SaveData>();
    80.  
    81.         foreach(ISaveable saveable in SaveManager.saveables)
    82.         {
    83.             saveDatas.Add(saveable.Save());
    84.         }
    85.  
    86.         return saveDatas;
    87.     }
    88.  
    89.     void SaveSelectedGame()
    90.     {
    91.         if(currentSelectedButton != null)
    92.         {
    93.             gameSavesDisplayButtons[currentSelectedButton].saveDatas = GetSaveDatas();
    94.             SaveManager.SaveGame(gameSavesDisplayButtons[currentSelectedButton]);
    95.             ShowGameSaves();
    96.         }
    97.     }
    98.  
    99.     void LoadSelectedGame()
    100.     {
    101.         if(currentSelectedButton != null)
    102.         {
    103.             //We need to make sure the scene fully loaded before we load our saved game state.
    104.             loadThisDataAfterSceneReset = gameSavesDisplayButtons[currentSelectedButton];
    105.  
    106. /*******Changed******/ SceneManager.LoadScene(loadThisDataAfterSceneReset.sceneName);
    107.         }
    108.     }
    109.  
    110.     void DeleteSelectedGame()
    111.     {
    112.         if(currentSelectedButton != null)
    113.         {
    114.             SaveManager.DeleteGameSave(gameSavesDisplayButtons[currentSelectedButton]);
    115.             ShowGameSaves();
    116.         }
    117.     }
    118.  
    119.     void DeleteAllSavedGames()
    120.     {
    121.         SaveManager.DeleteAllGameSaves();
    122.         ShowGameSaves();
    123.     }
    124.  
    125.     void OnGameSaveSelect()
    126.     {
    127.         if(currentSelectedButton != null) currentSelectedButton.GetComponent<Image>().color = Color.white;
    128.         currentSelectedButton = EventSystem.current.currentSelectedGameObject.GetComponent<Button>();
    129.         currentSelectedButton.GetComponent<Image>().color = Color.cyan;
    130.     }
    131.  
    132.     void ShowGameSaves()
    133.     {
    134.         foreach(RectTransform rect in gameSavesDisplayContainer)
    135.         {
    136.             GameObject.Destroy(rect.gameObject);
    137.         }
    138.  
    139.         List<GameSaveData> gameSaves = SaveManager.LoadGameSaves();
    140.  
    141.         for(int i = 0; i < gameSaves.Count; i++)
    142.         {
    143.             Button button = GameObject.Instantiate(gameSavesDisplayButtonPrefab);
    144.             button.GetComponentInChildren<Text>().text = gameSaves[i].saveName;
    145.             button.transform.SetParent(gameSavesDisplayContainer);
    146.             button.onClick.AddListener(OnGameSaveSelect);
    147.             gameSavesDisplayButtons.Add(button, gameSaves[i]);
    148.         }
    149.     }
    150. }

    I think thats everything. Remember, I have no idea if the way I am doing things is correct.
    Good luck =)
     

    Attached Files:

    hjohnsen likes this.
  2. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks so much HiddenMonk! It looks like I just had no clue how to place it all which is an indication of what a noob I am to saving and loading generally.

    I'll tell you what though, your code posting has been really helpful and the fact this is all located in one thread id probably going to help out a lot of newbies because we can just all refer to this thread whenever people ask about saving and loading, will have a look, if only there were somebody who had made a proper tutorial series on all of this because when I keep looking through the code while you can easily find out what individual lines mean I really don't understand how the professionals expect the newbies to simply work out code of this scale.

    In fact I think I'll put you in my game credits in the special thanks section because you've been seriously helpful lol :p

    For the record, your previous code worked perfectly, the only problem was because of the GetActiveScene code as I explained in other posts the gameobjects would instantiate on the scene you were at rather than load up the saved scene. So I ended up having all my gameobjects perfectly instantiating onto the title menu when I first ran the game and tried to load up.
     
  3. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Hehe, no need for that ^^, my knowledge came from just looking around the internet. Also remember, even that code I posted has issues that need to be addressed, and I dont know the proper way to handle it.

    The problem you were having was due to my original code not taking into account scene changing since it was mainly an example. To get scenes to work all we had to do was in the GameSaveData class have a string sceneName, then in the SaveUI class NewSaveGame method I store the current active scene name, and then finally in the SaveUI LoadSelectedGame method I load the scene using the sceneName we stored.

    However, since the scene has objects already in it, we now need a way to replace those objects with the saved objects, which I think I almost had a way, but duplicating objects in the editor breaks it. So that will be your mission now ^^.
    If there is a way to know what object was just duplicated and to grab its duplicate then we should be good to go, but I dont really know of a sure way.

    Edit - I also just noticed a bug in DestroyDuplicateObjects in the SaveManager class where we destroy the duplicate, but not remove from array so there can be a null reference. Ill fix it up tomorrow since its sleep time =).
     
    Last edited: Oct 2, 2016
  4. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Nah, I fully realise that looking at the code that you were going for a single scene example because I saw the GetActiveScene code and I thought I could fix it myself using the video I posted but as ever it's just too complex implementing it by yourself if you're a noob because of the scale.
     
  5. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I spent some more time and still cannot find a good way to handle duplicates =(, kinda giving up.

    Heres the fix to the null reference I mentioned in my prev post. Also I noticed another issue with if we were to apply changed to a prefab instance it would effect the original prefab, so I fixed that too.

    Code (CSharp):
    1.  
    2.  
    3. //This method is found in the SaveManager class
    4. static void DestroyDuplicateObjects(string uniqueID, SaveableGameObject[] alreadySpawnedSaveableObjects)
    5.     {
    6.         for(int i = 0; i < alreadySpawnedSaveableObjects.Length; i++)
    7.         {
    8.             if(alreadySpawnedSaveableObjects[i] != null && alreadySpawnedSaveableObjects[i].uniqueID != string.Empty && alreadySpawnedSaveableObjects[i].uniqueID == uniqueID)
    9.             {
    10.                 GameObject.Destroy(alreadySpawnedSaveableObjects[i].gameObject);
    11.                 alreadySpawnedSaveableObjects[i] = null; //Just in case destroy doesnt properly set to null.
    12.                 return;
    13.             }
    14.         }
    15.     }
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System;
    5. using System.Collections.Generic;
    6.  
    7. //********Must be put inside a folder named Editor
    8.  
    9. [InitializeOnLoad]
    10. public class SaveableGameObjectAutoSetup
    11. {
    12.     static SaveableGameObjectAutoSetup()
    13.     {
    14.         EditorApplication.hierarchyWindowChanged -= SetupSaveableGameObjects;
    15.         EditorApplication.hierarchyWindowChanged += SetupSaveableGameObjects;
    16.     }
    17.     static void SetupSaveableGameObjects()
    18.     {
    19.         SaveableGameObject[] saveables = GameObject.FindObjectsOfType<SaveableGameObject>();
    20.  
    21.         if(saveables.Length > 0)
    22.         {
    23.             GameObject saveablePrefab = (GameObject)PrefabUtility.GetPrefabParent(saveables[0].gameObject);
    24.            
    25.             if(saveablePrefab != null) //If we didnt create a prefab yet, then do not set the uniqueID so that the prefab will always have a empty value.
    26.             {
    27.                 SaveableGameObject prefabSaveable = saveablePrefab.GetComponent<SaveableGameObject>();
    28.                 if(prefabSaveable.uniqueID != string.Empty || prefabSaveable.originalPrefabNameReference != prefabSaveable.gameObject.name) //If I dont do this check, it seems the SetDirty causes hierarchyWindowChanged to constantly be called?
    29.                 {
    30.                     //We need to do this reset in case you changed a prefab instance and hit apply, which would have saved the instances unique ID to the prefab, which we dont want.
    31.                     prefabSaveable.uniqueID = string.Empty;
    32.                     prefabSaveable.originalPrefabNameReference = prefabSaveable.gameObject.name;
    33.                     EditorUtility.SetDirty(prefabSaveable);
    34.                 }
    35.                
    36.                 for(int i = 0; i < saveables.Length; i++)
    37.                 {
    38.                     SaveableGameObject saveable = saveables[i];
    39.  
    40.                     if(saveablePrefab != saveable.gameObject) //I dont think is needed since I dont think FindObjectsOfType returns prefabs.
    41.                     {
    42.                         saveable.originalPrefabNameReference = saveablePrefab.name;
    43.  
    44. //Need way to handle duplicates...
    45.                         if(saveable.uniqueID == string.Empty)
    46.                         {
    47.                             saveable.uniqueID = Guid.NewGuid().ToString();
    48.                         }
    49.  
    50.                         EditorUtility.SetDirty(saveable);
    51.                     }
    52.                 }
    53.             }
    54.         }
    55.     }
    56. }
     
  6. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I think I'm starting to actually understand this code better because now I've got everything out in front of me I can research the bits your commenting on and check out how they work myself.

    http://answers.unity3d.com/questions/1065737/how-to-destroy-the-previous-instance-of-prefab-at.html

    It seems we need for this to to find a way to grab the data of the gameobjects that were first made and then delete those gameobjects after the game has loaded so they don't duplicate themselves right? Correct me if I'm wrong, I'll try searching and see if I can help you find an answer.
     
    Last edited: Oct 12, 2016
  7. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    So as I was writing an explanation of the issue, I realized there is an easy fix.
    All we need to do is destroy all saveables from the scene before we load in the save.
    This might not be so great for performance depending on how many saveables you got, but its simple.

    So basically this change within our SaveManager
    Code (CSharp):
    1.  
    2.     public static void LoadGame(GameSaveData data)
    3.     {
    4.         if(data != null)
    5.         {
    6.             //Since we are going to load a new game, we need to clear the saveables since every object will be recreated.
    7.             saveables.Clear();
    8.  
    9.             SetGameSaveToFirstInList(data);
    10.  
    11. /* This is what we added */ DestroyDefaultSaveables();
    12.  
    13.             //Now we load in the objects.
    14.             for(int i = 0; i < data.saveDatas.Count; i++)
    15.             {
    16.                 ISaveable obj = ((GameObject)GameObject.Instantiate(Resources.Load(data.saveDatas[i].resourceName))).GetComponent<ISaveable>();
    17.                 obj.Load(data.saveDatas[i].xmlData);
    18.             }
    19.         }
    20.     }
    21.  
    22. /* This is the added method */
    23.     static void DestroyDefaultSaveables()
    24.     {
    25.         SaveableGameObject[] defaultSaveables = GameObject.FindObjectsOfType<SaveableGameObject>();
    26.      
    27.         for(int i = 0; i < defaultSaveables.Length; i++)
    28.         {
    29.             GameObject.Destroy(defaultSaveables[i].gameObject);
    30.         }
    31.     }

    Hopefully it should be working fine now.

    Ive removed the uniqueID code as well as added/edited some other codes. I didnt mark the changes, but I think the changes were made in these classes; SaveableGameObjectAutoSetup, SaveableGameObject, SaveManager, SaveData, SaveGameData.
    The SaveableGameObjectAutoSetup now just handles automatically setting the prefab name reference so you dont need to worry about it anymore, however, the way it does it might not be so great performance wise since whenever you edit the prefab in the editor its going to loop through all its instances to do checks. Its just in the editor though, so might not be so bad.

    Ill include the project files.
     

    Attached Files:

  8. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Oh I don't care as long as it's all working! As long as it's a complete code you see even as a noob I can go and research all the little bits of it and understand it all better like I'm doing now and improve on it myself. Thanks hiddenmonk, these mods are annoying lol, I've reported this thread in the hope that this gets stickied for other newbies but no luck so far and it's clearly an issue that the more experienced guys don't like to go through tons because of how big the scale is.


    Edit: Ahhhh! I was wondering about this when I looked at your code! As a noob I wasn't sure but am I right in thinking that you've gone and made a second array or something in order to store all the default objects and clear them so you don't have to worry about the new objects spawning on top of each other?

    I wasn't sure whether that was possible but it looks like it is.
     
    Last edited: Oct 12, 2016
  9. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,977
    It won't be stickied. It is just one specific topic among many related to scripting. While it is great that you have found some solutions that work for you, the bulk of the thread is just your own exploration. It is a topic that has many answers/approaches. Keep working on learning an approach that best fits your needs, but don't worry so much about other people needing/using your approach. Many have solutions already, and if they are so inclined, we have a search function on the forums.
     
    Kurt-Dekker likes this.
  10. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    It's just something that is so important for gaming and there simply aren't enough complete tutorials on, I actually tried looking at the Unity courseware and I trawled through books on Amazon and various tutorial sites for complete information on this topic and nothing and this is stuff being written up by professionals, the most promising thing I found was a 'complete' tutorial on saving and loading but it turned out that the guy had only done how to save variables and there were people who had provided the information he was wanting payment for for free.

    I just think it's a massive oversight on part of the Unity community and staff when this is such an important topic and we see millions of FPS tutorials and all sorts of other spam yet even when I'm happy to pay people money to get a nice tutorial written up or download I literally had to piss people off in order to get any kind of response about the actual process of programming all of this.

    It would have saved me and I suspect many other newbies going by the view count on this thread now many months of frustration if there was just one good tutorial on this subject and I just think that if people are going to get annoyed about having to explain it there should be information like this thread put up somewhere to make life easier for everybody.
     
    Last edited: Oct 12, 2016
  11. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Oh by the way hiddenmonk, yes you were right about the uniqueID thing, I tried putting it into my game and it caused all sorts of weirdness because for some reason the reference names would mixup with each other and cause my gameobjects to make weird amounts of duplicates of themselves when they should be all seperate from each other.

    Will give this code a test and see how well it all works.
     
  12. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I think you got the idea, but just to be clear...

    The DestroyDefaultSaveables method just uses unitys gameobject method FindObjectsOfType to gather all of the saveable components that are in the scene when you load the scene up, stores them in an array and we loop through it to delete them all so that we can then have all the save datas spawn in the proper objects, avoiding any duplicate issues.


    Unfortunately there are still many things that I dont know how to handle, such as what happens when you save a game then go in the scene and remove some saveable objects since you decided you didnt want them in that level anymore? Next time you load the save, it will still spawn in those objects you didnt want to be there.
    Plenty of issues to think of, but as a simple save/load I guess this can do.
     
    Last edited: Oct 12, 2016
  13. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Here are my .02 Euros on the topic, which specifically mentions saving GameObjects.

    The problem with saving Unity GameObjects, or any other Unity-specific class for that matter, is that these Types will cause serialization errors simply because a BinaryFormatter doesn't know how to serialize them. Now, for things like Vector3s, which came up near the beginning of this thread, it's relatively easy and has already been covered here and elsewhere; "somehow" convert the Vector3 to three floats, and rebuilt it again after loading. Quaternions etc. are handled similarly. Personally, I like using ISerializationSurrogates for this because it's used directly by the BF via a SurrogateSelector.

    If you are still new to serializing data, you will have already experienced the complexity of the topics involved. I experienced the same when I started reading up on it and wrote my own save & load system, which took hundreds of hours. I also wondered why there was no simple, free, and well-documented save and load asset available for Unity developers, seeing as saving and loading player progress is of critical importance for all but the most simple and shortest games. That's why I wrote something which I called Unity Save Load Utility, which in my opinion does exactly what I described above. You didn't want any asset advertising, even though USLU is completely free), but I'd like to think of it more of an interactive tutorial on saving and loading complex data in Unity than anything else. I will just point you to the two documents I wrote for it, which should explain a few things about how it works and maybe inspire you like UnitySerializer did for me (although I didn't have the luxury of detailed documentation; I had to read through thousands of lines of unfamiliar code from a professional programmer! ;) ). You can find the links to the documention at the end of this post, along with the asset itself which might also prove useful since it also has dozens of comment sections explaining steps in the code itself.

    Back to the topic of GameObjects, and Components, which are the two things which pose the biggest problems. The first step is to think about what kind of data needs to be saved, and what kind of (serializeable) data can be extracted from GameObjects and Components, and how data has to be converted to and from serializable form. These thoughts lie at the core when thinking about how to serialize data in Unity. If you already know how to serialize simple data (consisting of Types which are already serializable, i.e. Namespace System types and those that can be converted easily with a ISerializationSurrogate or whatever), then the fun part begins right after that.

    A GameObject is first and foremost just a name (string), an active state (bool), a layer and a tag (int and string). Easy enough. Of course, this is not enough to differentiate GameObejct when saving and loading, so you have to add this yourself; by giving each GameObejct a unique id (I use GUID), you can make sure the right data is put to the right GameObject after loading. You also need to know a GameObject's prefab name so it can be instantiated correctly after loading (a GameObject's name could change during play, but a prefab's name will always be the same).

    Then comes the _really_ fun part: Serializing components attached to a GameObject. There is one that could be handled easily if wished: Every GameObject always has a Transform component which only needs a few Vector3s and Quaterions saved and loaded. The rest is a different story. The most foolproof and simplest, albeit most tedious way is to hard-code the data conversion of each component. That means that for any Type of component, you write a seperate class which holds all the data from it, and also write a method which converts data from one instance to the other and back. This might even be the best way to go for very simple games, but if you continue developing, then it's good to have a solid foundation that can be expanded easily and is flexible enough that this foundation can be used for different projects. So you have to find a way to get all the data from all component Types and convert it in a way that is as generic as possible (and back, of course).

    I got inspired by UnitySerializer which was arguably on eof the most popular solutions a few years ago. It uses Reflection to go through each field of an instance and adds the field name to a dictionary as a key, and the field's value as the dictionary's value in the form of an object Type instance. If the field value itself has fields that need to be converted, then instead of the field value, a new dictionary is added as the first dictionary's value, and so on. This is how an entire component (or any instance, for that matter) hierarchy can be converted into serializable form. Of course, there are many special cases, especially when it comes to collections, but as a whole the system is relatively simple, if that can be said when talking about serialization in Unity.

    USLU Manual:
    https://www.dropbox.com/s/qu6dnxbgt77zz91/USLU_Manual.html?dl=0
    USLU QuickStart Guide:
    https://www.dropbox.com/s/ipdprxm96rd21nt/USLU_QuickStart.html?dl=0

    Unity Save Load Utility:
    https://forum.unity3d.com/threads/unity-save-load-utility-free-save-and-load-your-data.435506/
     
    Last edited: Oct 14, 2016
    Lethn, Dave-Carlile and HiddenMonk like this.
  14. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Sorry I hadn't responded to this big post, been quite busy now I've gotten so much out of the way.

    In regards to your utility Cherno I'll have a look at it and see how it all works, the main problem I had with people advertising assets is I could see they were rather obnoxiously trying to spam posts where newbies were genuinely trying to ask questions about the actual coding methods behind all of these functions and just how to get everything started so they can code it themselves. I've got no problem with free assets as long as they work and are updated properly which yours seems to be and I'd pay good money for assets that are properly done but the problem is the serialisation ones on the asset store are either barely updated or they have very poor documentation and tutorials when all you need is a decent walkthrough video to get an idea of how to use everything.

    I just think it's pretty dishonest and sneaky to go and spam paid for advertisements to take advantage of newbies who are genuinely trying to learn this code properly. So when you had those couple of posters immediately trying to get angsty with me over it just seemed pretty pathetic really and only proved my point.

    I just wanted to check in because in all the excitement of finally getting the damn gameobjects saved properly I had forgotten about implementing basic integer and float saving, luckily though there's a lot of documentation on that subject people posted here so I just wanted to ask if my code was linking up correctly for saving a simply score integer for example. After this my game will pretty much be there, I'm not getting any error codes so I may just need to test it and tweak things, I suspect I still might need to do some stuff in the gamemanager or UISave to have my score save properly.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3.  
    4. [Serializable]
    5. public class GameSaveData
    6. {
    7.     public string saveName;
    8.     public string sceneName;
    9.     public List<SaveData> saveDatas;
    10.     public int score;
    11.  
    12.     public GameSaveData(){}
    13.     public GameSaveData(string saveName, int score, string sceneName, List<SaveData> saveDatas)
    14.     {
    15.         this.saveName = saveName;
    16.         this.sceneName = sceneName;
    17.         this.saveDatas = saveDatas;
    18.         this.score = ScoreManager.score; // Grabs score integer from the Scoremanager and saves to file?
    19.     }
    20. }
     
    Last edited: Nov 16, 2016
  15. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I dont think you want to have the score in the GameSaveData, instead you want to have the score saved within the saveDatas list just like all your other saveable data.

    So just like our SpecialCube is a gameobject that inherits from SaveableGameObject, you would want your scoremanager either to also be a gameobject that inherits SaveableGameObject and not a static class, or keep it as a static class, but keep the score data on a SaveableGameObject so the static class can reference it. This way the score will be save in the scene properly with all the other saved datas. You can also look into things called "singletons", those might come in handy as well (they are kinda like a static gameobject).

    The GameSaveData should really only hold what is necessary to properly load the saveDatas stored in the GameSaveData, as well as some general info about the save (such as name, date/time save was made, etc..)

    Hopefully you get what I mean, and remember, there might be better ways to handle this, but this is just what I can think of =)
     
    Lethn likes this.
  16. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    So in an effort to understand HiddenMonk's code more I'm brushing up on my list knowledge in particular because I'm probably going to end up having to look at them no matter what if I want to make complete games regularly since I also want to have a scoreboard set up too.



    Now the video makes sense in the context of using a list by itself, however in the context of saving and loading it's more complicated for me as a new programmer because of the scale of everything. On the bright side, thanks to the tutorials I've been looking at and from me reading about list I think I have found the appropriate areas of code to add a simple integer to the savedatas list.

    Since I didn't understand this code a huge amount I thought I could just dump the integer in with the other data but that's clearly not how this works, I wanted to double check with people whether I was on the right track looking at this code.

    Code (CSharp):
    1.     List<SaveData> GetSaveDatas()
    2.     {
    3.         List<SaveData> saveDatas = new List<SaveData>();
    4.  
    5.         foreach(ISaveable saveable in SaveManager.saveables)
    6.         {
    7.          
    8.             saveDatas.Add(saveable.Save());
    9.         }
    10.  
    11.         return saveDatas;
    12.     }
    I get what HiddenMonk is saying about putting the integer in the list, I tried various combinations of saveDatas.Add ( scoremanager.core ()); but obviously that's wrong because the engine complains and doesn't even recognise the score integer. I also tried creating a brand new list as well but of course even if I could get that to work it would screw everything up because the two lists would come into conflict with each other and there is only one integer number I need to worry about here.

    Mind you, the problem could also just as easily be I'm just looking at the wrong damn section of code again.
     
  17. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    An example of what I meant was something like this
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. public class ScoreManager : SaveableGameObject
    6. {
    7.     public static int score;
    8.  
    9.     [Serializable]
    10.     public struct ScoreManagerSaveData
    11.     {
    12.         public int score;
    13.     }
    14.  
    15.     public override string SaveMe()
    16.     {
    17.         ScoreManagerSaveData scoreManagerSaveData = new ScoreManagerSaveData();
    18.         scoreManagerSaveData.score = score;
    19.  
    20.         return XMLSerialization.ToXMLString<ScoreManagerSaveData>(scoreManagerSaveData);
    21.     }
    22.  
    23.     public override void LoadMe(string xmlData)
    24.     {
    25.         ScoreManagerSaveData scoreManagerSaveData = XMLSerialization.StringToObject<ScoreManagerSaveData>(xmlData);
    26.         score = scoreManagerSaveData.score;
    27.     }
    28. }

    Basically, you need to create a SaveManager gameobject that you keep in every scene, or you can get into singletons.
    An example of a simple singleton you can use is this (not actually tested, so might not work with our save method..)
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. [DisallowMultipleComponent]
    5. public abstract class SingletonSaveableGameObject<T> : SaveableGameObject where T : MonoBehaviour
    6. {
    7.     public static T instance;
    8.  
    9.     void Awake()
    10.     {
    11.         if(instance != null)
    12.         {
    13.             DestroyImmediate(this);
    14.         }else{
    15.             instance = gameObject.GetComponent<T>();
    16.             Object.DontDestroyOnLoad(instance.gameObject);
    17.             OnAwake();
    18.         }
    19.     }
    20.  
    21.     public virtual void OnAwake(){}
    22. }

    That singleton does not create itself if it doesnt exist, so you need to make sure you place it on your main menu or something.
    A more advance singleton is this (I dont have it setup to be used with the saveablegameobject)
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    5. {
    6.     public static bool isApplicationQuitting {get; private set;} //use this to help prevent singleton from leaking after application ends
    7.  
    8.     public virtual bool dontDestroyOnLoad {get {return true;}}
    9.  
    10.     static T _instance;
    11.     public static T instance
    12.     {
    13.         get
    14.         {
    15.             if(isApplicationQuitting) return _instance;
    16.  
    17.             if(_instance == null)
    18.             {
    19.                 T[] objects = FindObjectsOfType<T>();
    20.                 if(objects.Length > 0)
    21.                 {
    22.                     _instance = objects[0];
    23.  
    24.                     if(objects.Length > 1)
    25.                     {
    26.                         Debug.LogWarning("Multiple instances of Singleton type \"" + typeof(T) + "\". Destroying all except the first.");
    27.                         for(int i = 1; i < objects.Length; i++)
    28.                         {
    29.                             DestroyImmediate(objects[i].gameObject);
    30.                         }
    31.                     }
    32.                     return _instance;
    33.                 }
    34.                 _instance = new GameObject("Singleton" + typeof(T).ToString()).AddComponent<T>();
    35.             }
    36.             return _instance;
    37.         }
    38.     }
    39.  
    40.     void Awake()
    41.     {
    42.         if(_instance != null)
    43.         {
    44.             DestroyImmediate(this);
    45.         }else{
    46.             _instance = instance;
    47.             if(dontDestroyOnLoad) Object.DontDestroyOnLoad(_instance.gameObject);
    48.             isApplicationQuitting = false;
    49.             OnAwake();
    50.         }
    51.     }
    52.  
    53.     public virtual void OnAwake(){}
    54.     public virtual void OnDestroyed(){}
    55.     public virtual void OnApplicationQuitting(){}
    56.  
    57.     void OnDestroy()
    58.     {
    59.         _instance = null;
    60.         OnDestroyed();
    61.     }
    62.  
    63.     void OnApplicationQuit()
    64.     {
    65.         isApplicationQuitting = true;
    66.         OnApplicationQuitting();
    67.     }
    68. }

    I have attached the project to show you the scoreboard example.
    Keep in mind that if you have any current save that was done before introducing this ScoreManager, when those saves get loaded, they will not have a save manager (it may appear they are working, but they wont get properly saved). So you will need to delete the saved and re-save with the ScoreManager in the scene.

    Once again, there are probably better ways to do all of this =)


    Note - I have made a small change to the SaveManager SaveGame method and SveUI NewGameSave method since I was noticing bugs...
    Code (CSharp):
    1.  
    2. public static void SaveGame(GameSaveData saveData)
    3.     {
    4.         //Changed start
    5.         if(gameSaves == null)
    6.         {
    7.             Debug.LogWarning("SaveManager cannot SaveGame until gameSaves list is initialized with LoadGameSaves");
    8.             return;
    9.         }
    10.         //Changed end
    11.  
    12.         if(saveData != null)
    13.         {
    14.             if(!gameSaves.Contains(saveData))
    15.             {
    16.                 gameSaves.Add(saveData);
    17.             }
    18.  
    19.             SetGameSaveToFirstInList(saveData);
    20.         }
    21.  
    22.         XMLSerialization.ToXMLFile(gameSaves, filePath);
    23.     }
    Code (CSharp):
    1.  
    2.     void NewSaveGame()
    3.     {
    4.         GameSaveData saveData = new GameSaveData();
    5.         saveData.sceneName = SceneManager.GetActiveScene().name;
    6.         saveData.saveName = newSaveNameInputField.text;
    7.         newSaveNameInputField.text = "";
    8.         saveData.saveDatas = GetSaveDatas();
    9. /****Added*****/ SaveManager.LoadGameSaves();
    10.         SaveManager.SaveGame(saveData);
    11.         ShowGameSaves();
    12.     }
     

    Attached Files:

    Lethn likes this.
  18. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks so much for the reply especially since it's almost Christmas and oh son of a whore, of course I was way off, I thought you were talking about simply dumping the integer to the file through the list that grabs all the other data, still got a long way to go before I properly understand the code.

    You didn't have to do the leaderboard or anything you know :D but thanks that code is actually far simpler for me to understand since it's just a separate list all by itself. It's just the sheer scale of the saving and loading code that confuses the hell out of me.

    I will be definitely though looking more and more at singletons, databases and lists in the future though so I can edit this code easier myself.
     
  19. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I hate having to beg people for code and explanation sometimes as a newbie but unfortunately if I can't find tutorials on it and so on I've got no choice. I'm doing all the other stuff like line of sight absolutely fine because there's tons of documentation and answers on it. Now that I see it all this makes perfect sense typically, so as you say, you're creating a saveablegameobject and is what is happening is that you link the scoremanager to that using scoremanager : saveablegameobject and then dumping it into a file? It seems this is the exact same principle as when we're writing the positions of gameobjects to a file and then instantiating them back into the game except adapted for the scoremanager empty by itself because it's an integer rather than coordinates.

    It looks like I wasn't hugely far off with the general theory but I thought you were supposed to do this with the list which is why the engine was complaining constantly, you can't really do that, either that or I just didn't have the right syntax.
     
  20. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Yup, I made a scoremanager prefab that I place in the scene as a saveablegameobject so it can be saved with everything else.
    I do not know if doing things that way would be good for things like scoreboards. For example, I dont think Id want to do it for something like general player options. Perhaps we should have another method for saving things like scoreboard, but that also depends on your scoreboard design, such as is it a scoreboard for every level, or does each level have its own scoreboard, etc...
    I would also not know how to tie it in with our current save method since our current save method only takes into account a single scene, which would really need extra work to be done to work with a single level having multiple scenes, etc...

    All in all, since my game has no need for a save system like this, I dont really have any good advise on how to handle it, so Im worried I am starting to bring you down a dark road where someone whos actually done this before could have cured everything =).
    My game only needs basic things to be saved, so I can easily just grab what I want and save it.

    Perhaps the scoreboard should just use a totally different save method, where it saves to its own file (such as playerprefs), and just use our current save method for things that need to be placed in the scene. That might be best.

    I think you need to first design how you want your save system to be like, what it needs to be able to do, etc..., then we can work on actually coding it.
    The current save system is really a hack job. Saving all game saves in a single file is really bad in my opinion.
     
    Last edited: Dec 21, 2016
  21. paragraph7

    paragraph7

    Joined:
    May 9, 2016
    Posts:
    8
    This thread caught my eye when researching for something else, and it's so long so I might be re-posting an earlier answer, but the way I do saving and loading is by doing a JSON class:
    Code (CSharp):
    1. public class PlayerJSON {
    2.     public string player_name;
    3.     public Vector3 player_position;
    4. }
    5.  
    6. public static class PlayerData {
    7.     public static string current_player_name;
    8.     public static Vector3 current_player_position;
    9.  
    10.     public static void Save() {
    11.         PlayerJSON pj = new PlayerJSON;
    12.         pj.player_name = current_player_name;
    13.         pj.player_position = current_player_position;
    14.      
    15.         string json = JsonUtility.ToJson(pj);
    16.         string path = Application.persistentDataPath + "name_of_json_file"
    17.  
    18.         File.WriteAllText(path, json);
    19.     }
    20.     public static void Load() {
    21.         string path = Application.persistentDataPath + "name_of_json_file";
    22.         string json = File.ReadAllText(path);
    23.         PlayerJSON p = JsonUtility.FromJson<PlayerJSON>(json);
    24.         current_player_position = p.player_position;
    25.         current_player_name = p.player_name;
    26.     }
    27. }
    28.  
    29.  
    This is a really crude example but I've managed to build large data structures to be saved and loaded using this approach. You just have to keep refreshing the PlayerData class with the information you want saved before calling the save method, and refresh the GameObjects with the data you need when loading. You shouldn't use statics either but this was the fastest way to demonstrate what I mean.

    I don't know if this helps - there were so many posts and I got a little confused so I mostly skimmed over the posts, but I figured this could help you in some way.
     
    Last edited: Dec 21, 2016
    Lethn likes this.
  22. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I made this thread to specifically compile as much information about saving and loading gameobjects as possible, anything you can post will help out so many newbies like me in the long run since it really is a very large topic to talk about but there aren't a lot of places where newbies can get started on it.

    It will also prevent the professional programmers from being annoyed at having to explain to people where to go in order to learn about this stuff.
     
  23. paragraph7

    paragraph7

    Joined:
    May 9, 2016
    Posts:
    8
    Well then, use the method I posted above. Write all the relevant data of a GameObject onto the JSON, and then upon loading either create a new empty GameObject and add all the necessary components, or load a prefab of one that has the necessary components, and then dump all that JSON data on that GameObject.
     
  24. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    So again, I get the general theory, it's just implementation that's proving difficult.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System;
    4. using UnityEngine.UI;
    5.  
    6. public class ScoreManagerEmptySave : SaveableGameObject {
    7.  
    8.  
    9.     public static int score;
    10.     Text scoreText;
    11.  
    12.     [Serializable]
    13.  
    14.     public struct ScoreManagerSaveData
    15.  
    16.     {
    17.         public static int score;
    18.  
    19.         void Start ()
    20.  
    21.         {
    22.             scoreText = GetComponent<Text> ();
    23.         }
    24.  
    25.         void Update () {
    26.  
    27.             if (score < 0)
    28.  
    29.             {
    30.                 score = 0;
    31.             }
    32.  
    33.             scoreText.text = " " + score;
    34.  
    35.  
    36.         }
    37.  
    38.         public static void AddPoints ( int pointsToAdd )
    39.  
    40.         {
    41.             score += pointsToAdd;
    42.         }
    43.  
    44.         public static void Reset ()
    45.  
    46.         {
    47.             score = 0;
    48.         }
    49.  
    50.     }
    51.  
    52.  
    53.     public override string SaveMe ()
    54.  
    55.     {
    56.         ScoreManagerSaveData scoreManagerSaveData = new ScoreManagerSaveData ();
    57.         scoreManagerSaveData.score = score;
    58.         return XMLSerialization.ToXMLString<ScoreManagerSaveData> (scoreManagerSaveData);
    59.     }
    60.  
    61.     public override void LoadMe (string xmlData)
    62.  
    63.     {
    64.         ScoreManagerSaveData scoreManagerSaveData = XMLSerialization.StringToObject<ScoreManagerSaveData> (xmlData);
    65.         score = scoreManagerSaveData.score;
    66.     }
    67.  
    68.  
    69.  
    70. }
    71.  
    I googled this error message as you do when debugging and I'm still searching for answers myself there seems to be varying answers on the subject but it mostly revolves around the engine not being sure about the stuff your accessing? However when I search up what an instance reference means people are saying you need to define the type, now I tried that on my code, but that's clearly wrong for what I'm doing because it just threw up even more errors on me.

    As you can see what I've done here Is I've dumped all my usual score code into the Serializable field, I hope it's not that causing it, because I of course need this to link to the text so that the player can actually see the score.

    Always one thing after another with this scale of code :p
     
  25. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    what error are you getting? Im assuming the text field is null? You put your Start and Update method inside the struct, which will not get called automatically. Put the start and update inside the class. You should also put all the methods in the class and not the struct as well.
    The struct is just an objects, so in order to use it, youll need to have an global instance of it somewhere in the class. We are making an instance of it in the save and load, but those are local and can just be accessed in the save and load. However, we just want to use that struct purely for serializing the int score, so just put everything in the class and just have the int score in the struct like I did, or is there a reason you want the methods in the struct?
     
    Last edited: Dec 23, 2016
  26. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Sorry, after your explanation I understand now what I was doing was total crap, to explain things more clearly I'm trying to make it so that whatever changes are made to the score as well as the text are saved. Because what's happening is that the actual score text that the player sees is deleting itself so I as well as the player have no way of keeping a record of it when the game gets loaded again.
     
  27. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Okay! So I think I have a much clearer understanding of how structs work and I realised after reading through both HiddenMonk's post and lots of documentation on structs and singletons why it isn't working. To be clear, I only understand why it isn't working, I don't know how to fix it myself which is annoying :( this is why I like following along tutorials.

    What I was trying to do was make the Text component update itself with the score int located within the Struct. However as you say Hiddenmonk you can't access variables within a struct because of the way it works. What I need is to be able to save these integers and also update other integers outside of the struct.

    You said that I need to make a global instance, I tried looking up what that is but unfortunately found very little basic explanations on the subject, I'm continuing to search for things but I have no idea how I would make that integer available to both the struct and various methods I'm constructing within the game. The reason I want all of these linked together would obviously be because when you load up the game you want your score to be displayed fully just the same way it was as before otherwise as it does now it simply resets itself to zero.

    Adding to this, if I were to make a bunch of other variables, for instance, if I were to add lives or ammo and so on or even health for later games then this would definitely need to be accessed from other methods as well because again, same problem, you just have all the variables resetting themselves even though the gameobject positions would have all been saved fine.

    I hope that clears things up, I'm slowly starting to understand at least the general theory behind all of this, but it's that classic problem of knowing nothing about the syntax as a newbie which is why I made the thread in the first place.
     
  28. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    First, I found a bug that might have caused trouble with saving.
    To fix it, in the SaveManager class I just had to change the SetGameSaveToFirstInList and OnSceneWasUnloaded methods.
    Here are the changes...
    Code (CSharp):
    1.  
    2.     static void OnSceneWasUnloaded(Scene scene)
    3.     {
    4.         saveables.Clear();
    5.         /***Removed this***/ //gameSaves.Clear();
    6.     }
    Code (CSharp):
    1.  
    2.     static void SetGameSaveToFirstInList(GameSaveData data)
    3.     {
    4.         int itemIndex = gameSaves.IndexOf(data);
    5.         if(itemIndex > 0)
    6.         {
    7.             gameSaves.RemoveAt(itemIndex);
    8.             gameSaves.Insert(0, data);
    9. /***Added this***/ SaveGame(null);
    10.         }
    11.     }

    Now on to your issue, assuming it was not related to the bug above...

    It should not be resetting to zero, though I think I see what is going wrong.
    Look at the LoadMe method
    Code (CSharp):
    1.  
    2.     public override void LoadMe(string xmlData)
    3.     {
    4.         ScoreManagerSaveData scoreManagerSaveData = XMLSerialization.StringToObject<ScoreManagerSaveData>(xmlData);
    5.         score = scoreManagerSaveData.score;
    6.     }
    Notice the line "score = scoreManagerSaveData.score;"
    score, in this case, is a global variable, since it is "public". In fact, its more than just global, its static. This means no matter how many instances (or GameObjects with components) of your ScoreManagerEmptySave class there are, that score variable will always be the same.
    So why use static? Well, its so you dont need to find the GameObject with the component ScoreManagerEmptySave and then do GetComponent, and then grab the score. Instead, you can just do ScoreManagerEmptySave.score and there you have it. Statics can cause trouble if you dont know what its doing. So for example, if you had 2 ScoreManagerEmptySave components in your scene and you were expecting to be able to change the score on them both separately, youll find out that when you change one, the other component also got changed, because the variable was static.

    All that said, when you call LoadMe, the score should be being set to what ever it was saved as.
    However, where I think you are going wrong is in your SaveMe method, you are doing this...
    Code (CSharp):
    1.     public override string SaveMe ()
    2.     {
    3.         ScoreManagerSaveData scoreManagerSaveData = new ScoreManagerSaveData ();
    4.         scoreManagerSaveData.score = score;
    5.         //.....
    6.     }
    You are creating a local variable of ScoreManagerSaveData, and then you are grabbing the score variable from the local scoreManagerSaveData. Now, in this case you had your struct use a static score variable, so creating the local variable wouldnt make the score 0, the score would indeed be whatever you set it too from any instance of that struct from anywhere in your code, and since you made your AddPoints method static, you could indeed add to that static variable without needing to create an instance of the struct.
    BUT, notice that you are then doing "scoreManagerSaveData.score = score". Based on your code, I can only assume you are adding and keeping track of your score from only within the ScoreManagerSaveData struct, which means the public static score variable within your ScoreManagerEmptySave component is never actually being set, which means it is always 0, which means when you do "scoreManagerSaveData.score = score" you are setting the score to 0.
    Confusing, huh =)

    What I dont understand is, why do you want to do everything within the struct?
    For example, why not have your class be like this...

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5. using UnityEngine.UI;
    6.  
    7. public class ScoreManagerEmptySave : SaveableGameObject
    8. {
    9.     public static int score;
    10.     Text scoreText;
    11.  
    12.     [Serializable]
    13.     public struct ScoreManagerSaveData
    14.     {
    15.         public int score;
    16.     }
    17.  
    18.     void Start ()
    19.     {
    20.         scoreText = GetComponent<Text>();
    21.     }
    22.  
    23.     void Update()
    24.     {
    25.         if(score < 0)
    26.         {
    27.             score = 0;
    28.         }
    29.  
    30.         scoreText.text = score.ToString();
    31.     }
    32.  
    33.     public static void AddPoints(int pointsToAdd)
    34.     {
    35.         score += pointsToAdd;
    36.     }
    37.  
    38.     public static void Reset()
    39.     {
    40.         score = 0;
    41.     }
    42.  
    43.     public override string SaveMe()
    44.     {
    45.         ScoreManagerSaveData scoreManagerSaveData = new ScoreManagerSaveData();
    46.         scoreManagerSaveData.score = score;
    47.         return XMLSerialization.ToXMLString<ScoreManagerSaveData>(scoreManagerSaveData);
    48.     }
    49.  
    50.     public override void LoadMe(string xmlData)
    51.     {
    52.         ScoreManagerSaveData scoreManagerSaveData = XMLSerialization.StringToObject<ScoreManagerSaveData>(xmlData);
    53.         score = scoreManagerSaveData.score;
    54.     }
    55. }
     
  29. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks again for the detailed explanation this is all really helpful, it looks like though I hadn't double checked my code in areas as well so I just made sure instead of screwing around I got your code in the ID system was completely missing so I doubt that would have helped anything.

    I don't necessarily 'want' that, I'm a total noob at this :p I just didn't know what was going on at first, I'm learning a hell of a lot more now though and I think I'm specifically going to read up like I said on structs, singletons and databases too possibly just so it's less of a headache to look through this stuff.

    You know something? I saw the score. try to autocomplete ToString for me which is very frustrating because I didn't know whether that would work or not, but ah well, this is why I made this thread to ask questions constantly about it. Unfortunately, I ran into a weird error after putting all of this in.

    Monodeveloper then pointed me to this line of code when I clicked on it.

    Code (CSharp):
    1. ISaveable obj = ((GameObject)GameObject.Instantiate(Resources.Load(data.saveDatas[i].resourceName))).GetComponent<ISaveable>();
    Now I had removed the [ReadOnly] commands because for some reason it was also throwing up errors for me but knowing me I probably just made everything worse by doing this and deleting something essential.

    I actually know what this error means! Normally it shows up when you don't enter in the correct using library right? Except I've directly copied from your code, so does this mean I've missed something out? There's only using.System; and using.UnityEngine; in there so I can't think of anything else it might be.
     
  30. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I think the problem is the data.saveDatas.resourceName is empty. Remember that in order to instantiate the proper Prefab, we needed to store the prefab name in our SaveableGameObject Component. If you look at the SaveableGameObject component, youll see the variable ontop that looks like this..
    Code (CSharp):
    1. [HideInInspector] public string originalPrefabNameReference;
    Now, since its hidden, how can you set it? Well I thought it would be annoying to remember to set the name of the prefab each time, so I had the file "SaveableGameObjectAutoSetup" made, which is (or should) be found in a folder named "Editor". That script should automatically handle filling out the originalPrefabNameReference for us, however, I have ran into an issue once where for some reason the name was not being set. Unfortunately when I went to investigate, I was not able to reproduce the issue =(.
    To check if the name being empty is the issue, go to the SaveableGameObject component script and remove the [HideInInspector] attribute and then check all your prefabs in your project (your project panel and your hierarchy panel). If you see that the originalPrefabNameReference is empty, then its probably that bug I noticed, but couldnt reproduce.
    Either that, or you didnt actually create a prefab yet =)


    I am not sure why it would do that. I actually dont think I am using the ReadOnly anymore, but if you do want to use it, in my code I had a file named "ReadOnlyAttribute". Now I noticed something just now which is in that file I had editor code mixed with the attribute class, which means in order to use it in non editor code, it must not be placed in a folder called "Editor", which means we would get an error when trying to build the game since UnityEditor namespace cannot be in the build. To fix this make sure you have a file named "ReadOnlyAttribute" NOT in a folder named "Editor", and a file named "ReadOnlyAttributeEditor" that MUST be placed in a folder named "Editor".
    Then in the "ReadOnlyAttribute" file put this tiny bit of code..
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class ReadOnlyAttribute : PropertyAttribute {}

    and in the "ReadOnlyAttributeEditor" file, put this code...
    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. //Taken from http://answers.unity3d.com/questions/489942/how-to-make-a-readonly-property-in-inspector.html
    6.  
    7. [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
    8. public class ReadOnlyDrawer : PropertyDrawer
    9. {
    10.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    11.     {
    12.         return EditorGUI.GetPropertyHeight(property, label, true);
    13.     }
    14.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    15.     {
    16.         GUI.enabled = false;
    17.         EditorGUI.PropertyField(position, property, label, true);
    18.         GUI.enabled = true;
    19.     }
    20. }
     
  31. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Okay so I fiddled around and I realised that some of the code I was using was out of date which of course would have contributed a lot to the problems I was having so I made sure that was put in, everything was a disorganised mess. I thought it was just me managing to screw things up again but I think I've run into another problem, it seems that for some reason in my scene if you have more than one prefab and try to name them all differently it overrides all the other prefab names, the code is all up to date now and I've double checked everything.

    So I do actually have an almost finished game here that I've been trying to test this code with this is why I've been so eager to get the saving and loading done. What I have done is I put in my player I then put in a cloud platform as normal and make sure both are in the resources folder but when I try to change the player's name it seems to default to the cloud platform as the prefab reference.

    I don't know whether I've screwed up somewhere or whether there might be a bug I've uncovered but hell it could easily be that because I'm really good at screwing things up for programmers generally. I'll upload a gif so you can see what's going on because I've gotten much better at recording my stuff now and I realised it would be better to just show you what's going on so you don't have to guess.

    https://gfycat.com/AllImpeccableBlowfish

    https://gfycat.com/CarefreeLividAfricanbushviper

    These gifs should be fine now.

    It seems to be overriding the last prefab that's been put in, rather than doing what it should do of using the prefab name references to distinguish between what objects are what. So this might go to explaining why when I put my carrot prefabs in they ended up overriding everything and causing the camera to disappear as well because of course the player ended up getting turned into a giant sodding carrot.

    Edit: wait a minute, gfycat went and cut down my video, just need to re-upload, I don't know why I didn't think of recording some stuff earlier really as it helps show things a lot clearer.
     
    Last edited: Jan 3, 2017
  32. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I am not seeing the issue on my side, and since there is a chance you might be using old/edited code, I cant properly tell what the issue is.
    I notice in your first gif where you try to change the prefab original name reference, that under it there is a "uniqueID" variable. That almost looks like the old attempt we did. Is that something you put in that is irrelevant, or is it indeed old code we had? If its old code, then its possible there is a lot more old stuff messing things up.

    Would you feel comfortable starting a conversation with me (go to your unity inbox and press "start a new conversation") and send your whole project over for me to check things out for myself? I completely understand if you are uncomfortable with that, but it would help a lot for me to just check things out for myself.
    Otherwise, I advise you to compare codes with the project ill put in this post and make sure they match up.
     

    Attached Files:

  33. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    It may be worth me simply doing that, because it could well be just old code if it's working fine for you, I'll check things out, overwrite the old code and report back, might not need to do anything at all, thanks for the link.
     
  34. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Okay, a bunch of stuff got fixed because I went through and double checked everything, so that's the good news, the saving and loading for all the other normal gameobject prefabs is working fine, the bad news is for some reason I still have issues with the UI Text deleting itself.

    I'll send over the project to you, please don't leak it or anything :p This is a full game and I pretty much just have some bits to finish and then that's it, it can be released, saving and loading has been the main issue for me which is why I made this thread.
     
  35. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I sent a detailed reply in our private conversation, but for those who want to know what the issue was, I think it was the missunderstanding that an object that is a child of a prefab, although in the project view it may look like a prefab, as far as I know, its actually not. The prefab is only the root object of the prefab, and you can only use Resources.Load with the prefab root name.
    Also, there were multiple SaveableGameObject components on a single prefab, which is not allowed.
    This save and load system relies on there being only 1 SaveableGameObject type component on the prefab and it must be on the root of the prefab.
     
  36. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks for all your help hiddemonk, I did get it all worked out and now it's actually saving and loading perfectly, even the multiple saves, here's what I did to solve the problem, I think you used a bit too much programming vocabulary in your explanation so I'll simplify it a bit for everyone else because it was a bit confusing at first :p I think a lot of professionals and experienced people often forget when they're talking to newbies about this.

    The reason the code I was using wasn't working was because the UI Text was deleting itself when you press load, that meant if you linked the scoremanager to the UI Text the scoremanager would also get deleted and therefore the score wouldn't work properly because it was all getting broken up.

    To fix this, I needed to have the scoremanager entirely seperate from the UI text and have it load in as a prefab like all the other gameobjects within the resources folder. However when I first did this, it caused a new problem of how to actually find the deleted and then loaded UI Text so that everything works. Then I remember gamesplusjames who had actually explained in a simpler way why it's a bad idea in cases to just use the public fields and so on to look for gameobjects or even tags and I had the scoremanager search for the uniquely named UI Text. Now because the scoremanager is doing that it will find the UIText regardless of what is happening in the scene so that should work no matter what changes are made to the integer.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System;
    4. using UnityEngine.UI;
    5.  
    6. public class ScoreManager : SaveableGameObject
    7.  
    8. {
    9.  
    10.  
    11.     public static int score;
    12.  
    13.     [Serializable]
    14.     public struct ScoreManagerSaveData
    15.     {
    16.         public int score;
    17.     }
    18.  
    19.     void Start ()
    20.  
    21.     {
    22.  
    23.     }
    24.  
    25.     void Update ()
    26.  
    27.     {
    28.  
    29.         Text scoreText = GameObject.Find ("scoreText").GetComponent<Text> (); // Simply add this to Void update and it will check constantly for your named text.
    30.  
    31.             if (score < 0)
    32.  
    33.             {
    34.                 score = 0;
    35.             }
    36.  
    37.                scoreText.text = score.ToString ();
    38.  
    39.     }
    40.  
    41.     public static void AddPoints ( int pointsToAdd )
    42.  
    43.     {
    44.         score += pointsToAdd;
    45.     }
    46.  
    47.     public static void Reset ()
    48.  
    49.     {
    50.         score = 0;
    51.     }
    52.  
    53.     public override string SaveMe()
    54.     {
    55.         ScoreManagerSaveData scoreManagerSaveData = new ScoreManagerSaveData();
    56.         scoreManagerSaveData.score = score;
    57.         return XMLSerialization.ToXMLString<ScoreManagerSaveData>(scoreManagerSaveData);
    58.     }
    59.  
    60.     public override void LoadMe(string xmlData)
    61.     {
    62.         ScoreManagerSaveData scoreManagerSaveData = XMLSerialization.StringToObject<ScoreManagerSaveData>(xmlData);
    63.         score = scoreManagerSaveData.score;
    64.     }
    65.  
    66.  
    67.  
    68.  
    69.  
    70. }
    71.  
    Very good explanation I found on searching for different objects within Unity generally which gave me more clarity: http://answers.unity3d.com/questions/1167757/how-to-get-a-singly-text-component-from-one-of-mul.html

    I'm sure there's more efficient ways to do this, like for example for certain integers if they aren't updated constantly have it do a check for the name only when the game loads, but this should be fine for now if you just want it all working. With this, the saving and loading code should all be complete.

    Thanks seriously for all your help HiddenMonk and the rest of the Unity community with their responses, I'll look at the other stuff later if I'm feeling patient enough as it looks interesting. I'm putting you in the credits whether you like it or not, I know it must be frustrating at times giving responses to this sort of thing and working out the bugs but to be honest you've probably got the best tester out there because I'm very good at causing software to break. If this code can survive me then that means this thread will probably help newbies out quite a lot for months to come so more experienced people can just link newbies here if they have questions about saving and loading and not have to constantly give very detailed explanations about it each time.

    I can understand why people were saying it was such a huge subject, it is isn't it? But I've been wanting to develop games properly for years and I wasn't going to let this stand in my way, this has actually been a childhood dream of mine and I do think that singleplayer games in particular need proper saving and loading mechanics regardless which is why I kept pushing people for information.

    If you guys still want to post up stuff about JSON or Binary Formatters, feel free, it's all really useful stuff to have good documentation and up to date code on!
     
    Last edited: Jan 6, 2017
  37. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    =) np, though once again, this save and load system is very basic. I can see it becoming an issue down the road with any updates to the game / scenes you do since their old saves dont know about the new stuff. There are probably many issues, but hopefully for a simple game it will be enough.

    Also, calling GameObject.Find every frame is very bad. Its better to call it once and cache the variable. Something like this...
    Code (CSharp):
    1.  
    2.     Text scoreText;
    3.  
    4.     void Start ()
    5.     {
    6.         scoreText = GameObject.Find ("scoreText").GetComponent<Text> ();
    7.     }
    8.  
    9.     void Update ()
    10.     {
    11.         if (score < 0)
    12.         {
    13.             score = 0;
    14.         }
    15.  
    16.        scoreText.text = score.ToString ();
    17.     }
    Youve tackled quite the project, though I fear this isnt goodbye. Bugs like to pop up ya know ^_~
     
  38. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Well the reason I didn't put it in start might interest you, I don't know whether this is anything to do with the version I'm using but if it's in start the scoreText goes red, for some reason unity isn't recognising the scoreText that's been created even though it's obviously there.

    I don't know why that is, I know there are sometimes issues where you have to put in custom functions with certain code and then put them in update and so on but I haven't heard of this type of thing happening before.

    Edit: Sorry, I went and misread the damn code didn't realise with this particular command you had to still declare the Text because I thought it was already declared in the start but I've remembered properly now looking at your code.

    Edit 2: Well how weird! It comes up with errors in this version of unity, I guess I'll have to keep it in update until I find out what's going on.
     
    Last edited: Jan 7, 2017
  39. Deleted User

    Deleted User

    Guest

    For the most part they're called keywords for future reference. As I am sure you've realized, you need to reduce what you want to save to data and create a way to restore that saves state from data.

    My suggestion is that you stop using code you don't fully understand and can't write on your own and start with the simplest use case, saving and restoring a single variable, and then move forward from there.

    If you have already built many systems you likely need to build a framework first and then integrate those systems with consideration for serialization.
     
    Last edited by a moderator: Jan 7, 2017
  40. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Well I understand it a hell of a lot better now thanks to HiddenMonk's explanations on various bits, for the rest of it, I'll be able to look up documentations on the specifics, I was going to have to learn this sooner or later in order to make a properly complete game, especially when it came to bigger projects for later on so I'm reading up on it as much as I can.

    On the bright side, snap to grid and features like that look incredibly easy now by comparison :p
     
  41. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    What kind of error?

    If you must, it would still be much better to have it be done like this...
    Code (CSharp):
    1.  
    2.     Text scoreText;
    3.  
    4.     void Start ()
    5.     {
    6.      
    7.     }
    8.  
    9.     void Update ()
    10.     {
    11.         if(scoreText == null) scoreText = GameObject.Find ("scoreText").GetComponent<Text> ();
    12.  
    13.         if (score < 0)
    14.         {
    15.             score = 0;
    16.         }
    17.  
    18.         scoreText.text = score.ToString ();
    19.     }

    This way, while it still needs to make sure its not null every frame, that is much much faster than calling GameObject.Find every frame since I think GameObject.Find basically needs to walk through all of the gameobjects in your scene until it finds one with the name you entered.
     
  42. RealSoftGames

    RealSoftGames

    Joined:
    Jun 8, 2014
    Posts:
    220
    Hey bud i have been tackling this issue for a little while and was origionally going to use C# binary serialisation to save my data, but the issue with that is, its tricky for beginers and its not humanly readable. so i have started to tackle XML as it is humanly readable and fairly easy to get started.

    here is a list of some of my findings over the last couple of days while researching, please correct me if im wrong so i can update this post to make it as accurate as possible for others.


    XML can serialise custom data structures as long as the class is tagged as [System.Serializable]

    allow me to paste in some examples.

    custome data structures such as this TestClass

    Code (CSharp):
    1. [Serializable]
    2. public class TestClass
    3. {
    4.     public string name;
    5.     public int age;
    6. }

    how to serialise using C# XML


    Code (CSharp):
    1. public class XMLSerializer : MonoBehaviour
    2. {
    3.     private void Start()
    4.     {
    5.          TestClass test = new TestClass();
    6.         test.name = "TestNAME";
    7.         test.age = 124;
    8.          Serialize serializer = new Serialize();
    9.         serializer.TestSave(test, "PlayerData");
    10.      }
    11. }

    RESULTS

    Code (CSharp):
    1. <?xml version="1.0" encoding="Windows-1252"?>
    2. <TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    3.   <name>TestNAME</name>
    4.   <age>124</age>
    5. </TestClass>

    the results will give you the saved structure for custom data in this example im only using a string and an int

    how ever serialising a Transform will not work or a gameobject because they are not marked as Serialisable.
    so we need to break down the object itself and obtain all of its components that are marked as serialisable and do the same thing as above.

    I am not sure if its possible to store all the data in a single save file and how it will work, but when i do figure it out i will upload a post on the forums for it.

    a work around i have found for saving the GameObjects Transform is to create a Serialisable transform Class, like such

    NOTE
    I have chosen to use the same naming convention as the Unity components. though be careful when doing so as it can get messy and attempt to use the UnityEngine.Vector3 or other structures. i sugest calling it serialisableTransform, serialisableVector3 etc...


    Code (CSharp):
    1. [Serializable]
    2. public class SerializableObject
    3. {  
    4.     public static Transform SerialiseTransform(UnityEngine.Transform goTransform)
    5.     {
    6.         Transform transform = new Transform();
    7.  
    8.         transform.position.x = goTransform.position.x;
    9.         transform.position.y = goTransform.position.y;
    10.         transform.position.z = goTransform.position.z;
    11.  
    12.         transform.rotation.x = goTransform.rotation.x;
    13.         transform.rotation.y = goTransform.rotation.y;
    14.         transform.rotation.z = goTransform.rotation.z;
    15.         transform.rotation.w = goTransform.rotation.w;
    16.  
    17.         transform.scale.x = goTransform.localScale.x;
    18.         transform.scale.y = goTransform.localScale.y;
    19.         transform.scale.z = goTransform.localScale.z;
    20.  
    21.         return transform;
    22.     }
    23.  
    24.     [Serializable]
    25.     public struct Transform
    26.     {
    27.         public Vector3 position;
    28.         public Quaternion rotation;
    29.         public Vector3 scale;
    30.     }
    31.  
    32.     [Serializable]
    33.     public struct Vector3
    34.     {
    35.         public float x, y, z;
    36.     }
    37.  
    38.     [Serializable]
    39.     public struct Quaternion
    40.     {
    41.         public float x, y, z, w;
    42.     }
    43. }


    now that we have our Serialisable Transform setup we can save it to disk.


    Code (CSharp):
    1. public class XMLSerializer : MonoBehaviour
    2. {
    3.     public GameObject goSerialized = null;
    4.    
    5.     private void Update()
    6.     {
    7.         if (Input.GetKeyDown(KeyCode.Space))
    8.         {
    9.             SerializableObject.Transform trans = new SerializableObject.Transform();
    10.             trans = SerializableObject.SerialiseTransform(goSerialized.transform);
    11.  
    12.             string path = Application.dataPath + "/" + "TestTransformSave.xml";          
    13.  
    14.             using (var stream = new FileStream(path, FileMode.Create))
    15.             {
    16.                 XmlSerializer xml = new XmlSerializer(trans.GetType());
    17.                 xml.Serialize(stream, trans);
    18.             }
    19.         }
    20.     }
    21. }
     
    Lethn and HiddenMonk like this.
  43. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks for the post, I know it's tough for beginners and I can understand why the more experienced programmers might hesitate, but honestly, if it's all up here and compiled then determined newbies like me can just keep reading through it all and experimenting until we get it rather than constantly making new topics and running into outdated code all the time like I did. People like me who genuinely want to learn don't give a crap about the difficulty.

    I really like how this thread has been turning out, thanks for all the help everyone and for your patience!
     
    RealSoftGames and HiddenMonk like this.
  44. Deleted User

    Deleted User

    Guest

    hi
    there is a question and what if you have one time unlocked a new level . and you dont need to unlock it second time .
    i hope it make sense to you.
     
  45. diwang

    diwang

    Joined:
    Sep 14, 2015
    Posts:
    30
    Here is the link to the video of official tutorial of what you're looking for , it might scary because it is 1 hour long but in the end it is simple to do it. https://learn.unity.com/tutorial/persistence-saving-and-loading-data .

    I'm going to post some simple example of saving and loading data with binary serialization , but please bear in mind that i'm a beginner too :) .

    Ok first of all , input this "using" inthe code that you gonna use for saving :
    1. using System.Runtime.Serialization.Formatters.Binary;
    2. using System.IO;
    ok so after that , the next step is pretty obvious, you need something to save. One of the way to do it is by having a class containing variable you want to save , then add [Serializable] in top of it , and that's it .

    ok ok you need example here is the example :
    1. [Serializable]
    2. public ClassToSave {
    3. public int variabletosave;
    4. }
    After you have class to save , now you need to write the code that save the data , and code that load the data. One of the way to do this is to have a method for it , yeah , just to make things easier. Then you need a directory to save it , i recommend saving it in Application.persistentDataPath . After that you need the name of the file .

    Here are the example of the uncomplete save method , finish it later. THIS EXAMPLE CODE WON'T WORK , JUST USE THE FINISHED CODE IF YOU WANT TO COPY PASTE.
    In this example i will use .dat as the extension of the file.
    1. public void Save(string customdirectory,string filename)
    2. {
    3. string path = Application.persistentDataPath + customdirectory + "/" + filename + ".dat".
    4. }
    5. //
    6. public ClassToSave Load(string customdirectory,string filename)
    7. {
    8. string path = Application.persistentDataPath + customdirectory + "/" + filename + ".dat".
    9. }
    Ok so for the next step , the next step is only one from multiple ways to save data, don't question why it use this and that because i'm also a beginner too all i know this thing work and nothing else(for now).

    Ok stop the chit chat , read carefully , for this step , firstly you need to create a string variable that combine the string to a single path , then you need to create the variable of the class you want to save/load and set the value to create new object of the class , then you set the value of the class variable to the value you want it to be when it is saved, then create FileStream variable , then use the variable by setting it as value to create the save file using File.Create for Save() , and File.Open() for Load() , then you create BinaryFormatter variable and set the value to create new BinaryFormatter object .

    Then the next step , for Save() method you call Serialize() function with the BinaryFormatter object , and for Load() , remember the variable you created before that contain the object of the class you want to load? now , by setting it as value of the variable , you call Deserialize() with the BinaryFormatter object, then cast the return type into the class you want to save.

    Then the last step , for both Load() and Save() , close the file stream by using FileStream variable you create earlier and call the Close() function. Then for Load() , use the variable that contain the class that has been loaded from file as the return value of the Load() method.


    Ok , nice , after these writing , i know you're looking for something easier to understand and easier to read xD okay okay here are the result of the whole script.
    1. using System.Runtime.Serialization.Formatters.Binary;
    2. using System.IO;
    3. //
    4. public class1 {
    5. public void Save(string customdirectory,string filename)
    6. {
    7. string path = Application.persistentDataPath + customdirectory + "/" + filename + ".dat";
    8. ClassToSave variable1 = new ClassToSave();
    9. variable1.variabletosave = 1; // set the value of all variable in the class
    10. FileStream variable2 = File.Create(path);
    11. BinaryFormatter variable3 = new BinaryFormatter();
    12. variable3.Serialize(variable1,variable2);
    13. variable2.Close();
    14. }
    15. //
    16. public ClassToSave Load(string customdirectory,string filename)
    17. {
    18. string path = Application.persistentDataPath + customdirectory + "/" + filename + ".dat";
    19. ClassToSave variable4 = new ClassToSave();
    20. FileStream variable5 = File.Open(path,FileMode.Open);
    21. BinaryFormatter variable6 = new BinaryFormatter ();
    22. variable4 = (ClassToSave) variable6.Deserialize(variable5);
    23. variable5.Close()
    24. return variable4;
    25. }
    26. //
    27. [Serializable]
    28. public ClassToSave {
    29. public int variabletosave;
    30. }
    Humph , just go Ctrl+C and Ctrl+V it .
     
    Last edited: Jan 2, 2020
  46. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,144
    If you're going to necro a post with helpful information (I didn't look over it to much, but seemed like you were trying) at least put your code in code tags...
     
    Bunny83 and eses like this.
  47. keola117

    keola117

    Joined:
    Sep 11, 2021
    Posts:
    15
    So, I know I'm really late to the party but, I figured i'd give the example that's pretty much the easiest (for future viewers) and usable for both offline/online saves (Depending if you are saving to playerpreference or, a database.) What you do is you get rid of mono behaviour in a class, go up to the top and add in using system; then, you make a struct(I prefer them over class) and you do this, [serialize] public struct SavedataTypes{ public/protected/private enum/string DataType; public/proteced/private string DataName; public/private/protected List<Int> DataValues; } then you do a public or, [Serializeable] SavedataTypes saveDataOfype(enum/string dataType, string dataName, List<Int> dataVal){ DataType = dataType; Dataname = dataName; DataValues = dataVal;} hen, you save this via playpreferences or, to a data list, since the data is unpacked it will save easy. also, you'll need to cast any enums to the playerfabs/data base like this. EnumStringCast = Enum.type.Enumtype.ToString; I got a headache so, thats about it for now but, I'm sending very complicated data like Creditcard info to a pay database etc and i gotta use 128bytes etc for encription but, thats not what you need. so, i have a lot of experience saving stuff etc and by far, serializing it into a stuct you can break apart into smaller data is the easiest way.
     
  48. keola117

    keola117

    Joined:
    Sep 11, 2021
    Posts:
    15
    a persistent path that is auto generated is shorter and easier for what he needs, Also, player preferences comes included with unity and saves time, Also, you dont need to hope around as much, you can save the info by converting data first and that also saves readability ex,

    //[SerializeField]
    //protected dataTosave = new dataTosave // my struct ref from earlier. casting newdata is betr so, going to do it that //way..



    public void SaveData(dataTosave newData){
    //first the hardestOne
    //1 string StatNameCast = newData.Enum.Type.ToString;
    PlayerPrefs.SetInt("DataIntValueChain"+newData.Name, DataintValues.Count); // this way data is automatically //seialized to beunique. because name is a string, if its not. validate it at //1

    for(int i = 0; i < DataintValues; i++){

    PlayerPrefs.SetInt("DataintValues_" + i, DataintValues[eye]); /*replace eye with i. the forum keeps removing it if i put i. i tested in compiler and it works. unity.forums just being freaking weird. */

    PlayerPrefs.SetString("NameType" + StatNameCast, StateNameCast/newData.Name);

    //+ whatever basic data types bool,string,int,float. give it a unique name so it knows what to grab in case of a load. //using the + sybol and a string of whatever name you gave your serialize struct.

    PlayerPref.Save();
    }

    and thats it. you saved a data type. and to get the data. you just validate it the same way or, if you dont mind overriding you can simply put this in the same file,

    void LoadData(dataTosave loadData){
    myStatToLoad = PlayerPrefs.GetString("NameType" + loadData.name); //this way it stays relevent to whatever struct you are currently referencing)


    loaData.name = myStatToLoad;

    }



    there might be a few typos in this but, you get the idea. this compresses what most people replies were into a savecode thats is 1/3 the size, and the thing is, you can typecast the name, then 128byte the data to encrypt it while still using playerPref. that way you dont have to even touch the file directory. would still have the serialize and deserielize at that point though. which, i wont get into cause you asked for simple. this is simple. the MOST simple. lol
     
    Last edited: Sep 27, 2021
  49. keola117

    keola117

    Joined:
    Sep 11, 2021
    Posts:
    15
    oh PS, if you wanna deal with dupes my way, just make a another struct variable Called ID. have it where the ID is something you controll, if its specific to an enemy, then cast enemy first with its oen unique ID. the id can get casted in scene and check its id aginst other t mae sure its unique, same with weapons. etc. but, weapons are better to cast against yourself/you player name. i made a rpg, with a saved int caled "timesGenerated" so, everytime a new object was spawned it would playerprefs.LoadThe last generted size and then +1. that way the ID was ALWAYS unique. and was handled by the system out of game.