Search Unity

Persistent Data Storage & Loading World Objects/Props

Discussion in 'General Discussion' started by winterfluxstudio, May 15, 2018.

  1. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    I've been looking at different ways of saving and loading a game under different parameters but don't quite understand how it's supposed to work for "large" games with a lot of stuff to handle.

    For example, using persistent data, the player could start a new game and then level their charatcers XP up, quit playing the game and, when they return in an hour, we can load their XP from the .dat file. All good.

    PlayerData.dat
    GameData.dat

    But what about objects in the world?

    Let's say I make a really basic scene ("MyScene"). All it has is a player and 1 cube. The player has the ability to instantiate cubes by pressing F or something and gets 10xp each time... so they start a new game and instantiate 5 cubes (so there are now 6 in total). and have 50XP.

    However, the original scene (Myscene.unity) that was loaded only has 1 cube placed. I understand how I can load their XP back into the game, but how do I handle loading "MyScene". AFAIK, the "Saved" .unity file still only has 1 cube. How do I tell Unity when loading the scene "Hey, last session they created another 5 cubes! so they need to be in the scene too".

    I hope that makes sense. I can't find too much information on this subject and the last few attempts have failed to provide the solution I want.

    I originally thought about maybe storing instantiated objects (and objects in general) in some type of list with a reference to their transform position in a WorldData.dat file. This way it might be possible to say "ok, there were 5 additional cubes that were added last session and they need to go here, here, here and here" etc.

    Anyone dealt with a similar scenario before? [an example of saving and loading the Game Year/Month/Day


    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Runtime.Serialization.Formatters.Binary;
    4. using System.IO;
    5.  
    6. public class CalendarData : MonoBehaviour {
    7.  
    8.     public int GameYear;
    9.     public int GameMonth;
    10.     public int GameDay;
    11.  
    12.     private void Start()
    13.     {
    14.         // if CalendarData.dat exists - load it.
    15.         if (File.Exists(Application.persistentDataPath + "/CalendarData.dat") == true)
    16.         {
    17.             Load();
    18.         }
    19.        
    20.     }
    21.  
    22.     // Load Data
    23.     public void Load()
    24.     {
    25.         if(File.Exists(Application.persistentDataPath + "/CalendarData.dat"))
    26.         {
    27.             // create new binary formatter
    28.             BinaryFormatter CalendarDataBF = new BinaryFormatter();
    29.             // open file
    30.             FileStream CalendarDataFS = File.Open(Application.persistentDataPath + "/CalendarData.dat", FileMode.Open);
    31.             // deserialize data
    32.             CalendarDataContainer calendarData = (CalendarDataContainer)CalendarDataBF.Deserialize(CalendarDataFS);
    33.             // close file
    34.             CalendarDataFS.Close();
    35.  
    36.             // data to load
    37.             GameYear = calendarData.GameYear;
    38.             GameMonth = calendarData.GameMonth;
    39.             GameDay = calendarData.GameDay;
    40.         }
    41.     }
    42.  
    43.     // Save Data
    44.     public void Save()
    45.     {
    46.         // create new binary formatter
    47.         BinaryFormatter CalendarDataBF = new BinaryFormatter();
    48.         // create new filestream
    49.         FileStream CalendarDataFS = File.Create(Application.persistentDataPath + "/CalendarData.dat");
    50.         // data to save
    51.         CalendarDataContainer calendarData = new CalendarDataContainer();
    52.         calendarData.GameYear = GameYear;
    53.         calendarData.GameMonth = GameMonth;
    54.         calendarData.GameDay = GameDay;
    55.  
    56.         // serialize data
    57.         CalendarDataBF.Serialize(CalendarDataFS, calendarData);
    58.         // close file
    59.         CalendarDataFS.Close();
    60.     }
    61.  
    62. }
    63.  
    64. // serializable data container class
    65. [Serializable]
    66. class CalendarDataContainer
    67. {
    68.     public int GameYear;
    69.     public int GameMonth;
    70.     public int GameDay;
    71. }
    I'm curious if there is something I am missing when it comes to persistently saving and loading GameObjects in the world, rather than just "data" in the pure sense.
     
  2. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    You may check out a free saving and loading parser I made awhile back.
    It's free and under MIT License, so you can do whatever you want with it.

    https://forum.unity.com/threads/public-open-source-save-loading-parser.474015/

    Requires no serialization whatsoever. Why people use serialization beats the heck out of me, when you can make life much more simple just saving and loading from a file creating your own format. Granted, this system is just basic. Just read the page and it will tell you the things it works with, and if anything, you can read the code and learn from it.

    This system has been recommended by a few people, so it's pretty good.
     
    Ryiah likes this.
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    The typical approach involves serializing everything necessary to restore the game state to disk, in a format such as json.
     
  4. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61

    I'm certainly not an expert on the topic but I'm pretty sure it's required to Load/Save and persist data in a secure manner. The resulting file is a Binary file which you can't just open up. AFAIK it's lightyears more secure than using something similar to playerprefs or just storing it in a plain textfile.

    https://docs.unity3d.com/Manual/script-Serialization.html

    "Unity uses serialization to load and save Scenes, Assets, and AssetBundles to and from your computer’s hard drive. This includes data saved in your own scripting API objects such as MonoBehaviour components and ScriptableObjects."

    and, unless I read that wrong, it's a necessary thing if you want to load/save scenes/assets

    https://forum.unity.com/threads/serialization-why-should-i-use-it.461154/
    This recent thread also discusses why people might use it.
     
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,205
    It's not required. You can very easily write your own custom binary format. If anything it will be more secure than if you used serialization.

    Why do I say it will be more secure? Because the file format for binary serialization is published. A commonly used format will always be better understood than a custom one.

    https://msdn.microsoft.com/en-us/library/cc236844.aspx
    https://stackoverflow.com/questions/3052202/how-to-analyse-contents-of-binary-serialization-stream
     
    winterfluxstudio likes this.
  6. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    Yeah, currently using .dat files as storage, just struggling on how to implement it in reality.

    https://docs.unity3d.com/Manual/script-Serialization.html

    "Unity uses serialization to load and save Scenes, Assets, and AssetBundles to and from your computer’s hard drive. This includes data saved in your own scripting API objects such as MonoBehaviour components and ScriptableObjects."

    I was hoping it might be possible to make a clone of the scene and save it on the player's computer.

    Original scene: MyScene
    [Player does some stuff. there's new objects in the word)
    Scene saved as: MySceneVar2

    Then MySceneVar2 could be loaded, rather than loading MyScene and loading all the objects back into the world.
     
  7. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    That's a good point. Although I would be concerned about the additional problems I might encounter running a custom implementation. I did something similar in Ruby on Rails and, instead of using Devise, I wrote my own custom encryption but it was a nightmare to work with after the project (an internal platform) got past a certain size.

    Thanks for the advice. I will look into writing my own and see how it goes. But, I would like to figure out my first problem before diving into something like that.
     
  8. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884

    Honestly, I view serialization as a 'poor-mans saving system', not as in poor as in broke (money), but as in, it's just a means to an end that works, without most people understanding why it works other than it works . Not every case is the same though, ultimately it depends what you are wanting to do with the data. The saving and loading system I made is technically serializing, like what makes XML secure? Binary Files, what makes them secure? Really, nothing does. No file is ever secure no matter how hard you try to make it impossible.

    XML is plain text, and a BinaryFormatter, all it takes is somebody that understands the fragmentation of the binary data to break it. Even a lot of times a lot of people don't encrypt their data, which for a single player game, why would you? Now if you're dealing with a multiplayer game, then you never put the save file on their system anyways, except for bare essentials such as video settings they set, etc. Otherwise you just feed the needed data from a backend database and done.

    But now, this begs the question, if the game is single player, do you really need to mask data they are saving?
    If it's multiplayer, then you don't need save files at all and just deal with backend systems that do all the work.

    So that leads to the point being, security for how much health the player has in single player game, is not really so essential, let them cheat if it's single player. It brings your game replay-ability letting users do what they want.
     
    Kiwasi and winterfluxstudio like this.
  9. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    Yes, Ironically the Unity video tutorial on the subject basically says "I'm not going to actually tell you how it works or why. just know that it does and can be used for saving/loading persistent data". which is quite frustrating.

    Yes.There are tons of reasons you would do that. anything in a game that you wouldn't want the player to change, they shouldnt be able to access in the same way they would playerprefs.

    Single player game with a multiplayer scoreboard based on total XP?. You could just cheat and change xp to 1,000,000 if it wasn't secure.

    or maybe a game that involves a time-based system (such as the script I posted). if the player could just change the day/month/year then it breaks the game.

    or even a build/progression game. If you can just change how much cash you have to $99,999,999 then you could just buy everything and power level to the end-of-game stuff in minutes.

    Well, then I guess it's down to personal choice but for the most part, I would rather keep it secure so the user cant just change it (unless you have some kind of in-game cheat mode that they can activate in a sandbox)
     
  10. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    @winterfluxstudio : Yeah I know hahahaha, they do that at times in their videos lol.

    Well you're missing one key point about saving files.
    If the player has a save file on their system no matter how much encryption you put into it, even some CIA Encryption, it will be broken into, it might fend off the lightweights, but the serious people who get off on doing it, will do it.

    But a couple problems with the examples you posted.

    Scoreboards: if the scores reflect online, then you don't store that data locally on the players system, you store it on a server-side database.

    The time system: you can have at the top of a save file just a little warning to not mess with things unless they know what they are doing or it might break the save file.

    Now the progression system: if this reflects online, then as the same as Scoreboards, this stuff isn't to be saved locally on the client and must be saved on a server-side database.

    I'm not trying to tell you how to do this, just trying to give some helpful advice that's all.
    It sounds like your game will be dealing with some form of online activity, whether that's from a scoreboard, to a progression system. These are the types of things you don't want to store in a save file. I mean I guess you could, just make it where it tries to load it first, before wasting online bandwidth, so have them cross check, if they match, then good to go, if not, force load the online data to replace it.


    But yeah, with this being said, I believe you should be looking for saving data online in a database.
    Because the stuff you're describing sounds like you're going to have some form of online to you're game.
     
  11. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    Another pet peeve from Unity "Here's how to implement a multiplayer mechanic... but we're going to do it in a single player environment and not show you how to actually do it as a multiplayer system"

    Yeah that is true but 9/10 players are not going to have any clue how to do that stuff, and if you need to protect against that then I can see more complex solutions being a better idea.

    I appreciate a different point of view. The game (whether I actually release it is undecided yet) is basically a "indie tycoon" game where you run your own studio etc. so theres a Calendar system to track the day/month/year.

    so, my point be that, for instance, let's say (in the game) it takes X amount of time to actually develop it (so the player risks salary costs, rent etc during development).

    If the player could just change the Day, Month or Year - it ruins the entire system. You could argue that in a single-player game it doesnt matter. if they want to cheat it has no impact on anyone else. But, I feel it would cheapen it and considering that it's fairly easy to keep that data protected, to the point where 9/10 of you average players dont know how to change the data, then it's worth doing.

    To be honest I'm not diving into MP yet. Although the pricing does look ok

    monthly active players:
    100,000
    Concurrent players:
    1,000
    Bandwidth/player:
    500B/sec
    Total bandwidth/month:
    1.22GB
    Price per GB:
    $0.49
    100,000
    Estimated players/month

    $59.96/month
     

    Attached Files:

    • ex.png
      ex.png
      File size:
      712.8 KB
      Views:
      976
  12. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    @winterfluxstudio : I hear ya mate. I do wish ya the best with your game though.
    But yeah, there's always encryption as well, which I do offer with my saving and loading system, at the very least you can pick up on some things I did, such as that to make your saves at least a bit more secure. I didn't do any extravagant encryption, just something that's better than nothing.
     
  13. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,205
    Just a couple things to keep in mind though. For starters, it only takes a handful of people causing trouble to make it bad for everyone. Furthermore all it would take to create a really nasty situation is for one of those few to publish the method they used and/or a program to automate the entire process. Trainer apps are very common in the cheat communities.
     
    N1warhead likes this.
  14. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,706
    Encryption's probably putting the cart before the horse at this point. If you modularize, it'll be easier to add encryption later, once you can actually save & load large gameworld data at all. For example, you could have a stack of code units like this:
    • Individual data points: A single object's position, or the existence of a newly-spawned object, etc.
    • Compile: Maintain the combined state of all individual data points as a single big data object.
    • Serialization: Serialize to, say, JSON for easy debugging (easily swapped out for binary serialization for release)
    • Data storage: Write to local disk (easily swapped out for a unit that writes to another destination and/or encrypts)
    Each of those units can be completely decoupled from the others and just treat the others as black boxes. For example, the Serialization unit can just grab the Compile unit's big data object and serialize it. It doesn't have to be concerned about how to generate that data object or what to do with it after serialization. You can always insert another unit between Serialization and Data Storage that encrypts.

    BTW, a tip: Your Compile unit will need to maintain data points not only in the current scene/zone/sector but also in other scenes that the player can return to. You'll probably want to distinguish between data points that persist across all scene changes (such as the alive/dead status of a major NPC) and data points that only need to be maintained in the current scene (such as the position of a random wandering mob). This keeps your data size manageable.
     
  15. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    I appreciate all of the insights that you and other people have given.

    I've come up with a sort of hackish way of doing what I need that I will iterate on.

    I basically decided to go the modular route and have one overarching "GameWorldState" script that's sole purpose is to load the "additional" gameobjects into the world, on the basis of everything being a prefab.

    1) User starts new game. There is 1 cube
    2) User instantiates 5 cube prefabs of type A, and another 5 cube prefabs of type B
    3) GameStateTypeA.cs and GameStateTypeB.cs record the new objects.
    4) Player quits and comes back an hour later. presses "Load Game" Button
    5) Default "New" game scene is loaded asynchronously, but after its finished the game is not loaded
    6) Instead, GameWorldState.cs kicks in and checks what needs to be in the world, then instantiates all of the
    prefabs at their correct position, with the correct attributes (as the vars in the attached scripts are public)
    7) User is loaded into saved scene with all of the cubes that existed when they left.

    I'm not sure how well it might work on scale, but for now it seems to be doing a good job.
     
  16. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    Sounds a lot like what my system did lol.
    Perhaps you got the idea from it maybe???

    Sense it's under MIT, feel free to change it to your hearts desire, do the BinaryFormatter if you like, just change out the encryption stuff and use that. So you can save to plain text for debugging and switch to the BF when you ready to release or whatever.
     
  17. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    No I haven't had a chance to take a look at your link yet. It's basically an extension of the system I already built (based on the https://unity3d.com/learn/tutorials/topics/scripting/persistence-saving-and-loading-data tutorial)

    so I have stuff like

    CalendarData.dat
    StudioData.dat


    and I just extended it to have a kind of "master" .dat file that manages new objects in the scene.

    It's a bit long but heres my MainMenuManager Script that takes care of loading the .DAT stuff.

    Curious to see what you changed compared to Unity's version. This stuff is still a little new to me. I'm still using the basic principles in the video.. just with loads of data.


    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using System.IO;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class MainMenuManager : MonoBehaviour {
    8.  
    9.     // Data Containers
    10.     public GameObject CalendarDataGO;
    11.     public GameObject StudioDataGO;
    12.     public GameObject GameDataGO;
    13.  
    14.     // Data Classes
    15.     private CalendarData calendarDataClass;
    16.     private StudioData studioDataClass;
    17.     private GameData gameDataClass;
    18.  
    19.     // Menu Screens
    20.     public GameObject MainMenuScreen;
    21.     public GameObject QuitConfirmScreen;
    22.     public GameObject StudioSetupScreen;
    23.     public GameObject ConfirmFileDeleteScreen;
    24.     public GameObject SettingsScreen;
    25.  
    26.     // Main Menu Screen Buttons
    27.     public Button NewGameButton;
    28.     public Button LoadGameButton;
    29.     public Button SettingsButton;
    30.     public Button TutorialButton;
    31.     public Button QuitButton;
    32.  
    33.     // Confirm Quit Screen
    34.     public Button QuitConfirmYesButton;
    35.     public Button QuitConfirmNoButton;
    36.  
    37.     // Studio Setup Screen Buttons
    38.     public Button StudioSetupBackButton;
    39.     public Button PlayGameButton;
    40.     public Dropdown StartingFunds;
    41.     public Dropdown GameDuration;
    42.     public Dropdown AutoSaveInterval;
    43.  
    44.     // Settings Screen Buttons
    45.     public Button SettingsBackButton;
    46.     public Button SettingsSaveButton;
    47.  
    48.     // Confirm Delete Files Screen
    49.     public Button ProceedYesButton;
    50.     public Button ProceedNoButton;
    51.  
    52.     // Loading Screen
    53.     public GameObject LoadingScreen;
    54.     public GameObject LoadingBarGO;
    55.     public Slider LoadingBar;
    56.  
    57.  
    58.     private void Start () {
    59.  
    60.         InitializeDataClasses();
    61.         InitializeMenuButtons();
    62.  
    63.     }
    64.  
    65.     private void Update()
    66.     {
    67.         // needs to be in update
    68.  
    69.         // possible for user to select "new game" button, delete previous save
    70.         // and then return to the main menu
    71.  
    72.         // check for one of the .dat files (if one exists; they all exist).
    73.  
    74.         // This also means SettingsData.dat does not trigger the bool on it's own, making the
    75.         // Load Game button interactable, even though there might not any other .dat files
    76.  
    77.         // (new player, changes and saves settings before starting their first game would generate
    78.         // a SettingsData.dat file, but not any of the other actual game save files)
    79.         if (File.Exists(Application.persistentDataPath + "/CalendarData.dat") == true)
    80.         {
    81.             LoadGameButton.interactable = true;
    82.         }
    83.         else
    84.         {
    85.             LoadGameButton.interactable = false;
    86.         }
    87.     }
    88.  
    89.     //--------------------------------//
    90.     private void PlayNewGame()
    91.     {
    92.         // populate data files with required information (default values)
    93.  
    94.         //[CalendarData.dat]
    95.         calendarDataClass.GameYear = 0;
    96.         calendarDataClass.GameMonth = 1;
    97.         calendarDataClass.GameDay = 1;
    98.  
    99.         //[StudioData.dat]
    100.         studioDataClass.StudioName = "";
    101.         studioDataClass.StudioReputation = 0;
    102.  
    103.         if (StartingFunds.value == 0)
    104.         {
    105.             studioDataClass.AvailableCash = 100000; // $100,000
    106.         }
    107.  
    108.         if (StartingFunds.value == 1)
    109.         {
    110.             studioDataClass.AvailableCash = 40000; // $40,000
    111.         }
    112.  
    113.         if (StartingFunds.value == 2)
    114.         {
    115.             studioDataClass.AvailableCash = 10000; // $10,000
    116.         }
    117.  
    118.         studioDataClass.Debt = 0;
    119.  
    120.         studioDataClass.hasPublisher = false;
    121.         studioDataClass.Publisher = "";
    122.  
    123.  
    124.         //[GameData.dat]
    125.  
    126.         if (GameDuration.value == 0)
    127.         {
    128.             gameDataClass.GameDuration = 9999; // 9,999 Years
    129.         }
    130.    
    131.         if (GameDuration.value == 1)
    132.         {
    133.             gameDataClass.GameDuration = 100; // 100 years
    134.         }
    135.  
    136.         if (GameDuration.value == 2)
    137.         {
    138.             gameDataClass.GameDuration = 75; // 75 years
    139.         }
    140.  
    141.         if (GameDuration.value == 3)
    142.         {
    143.             gameDataClass.GameDuration = 50; // 50 years
    144.         }
    145.  
    146.         if (GameDuration.value == 4)
    147.         {
    148.             gameDataClass.GameDuration = 25; // 25 years
    149.         }
    150.  
    151.         if (GameDuration.value == 5)
    152.         {
    153.             gameDataClass.GameDuration = 10; // 10 years
    154.         }
    155.  
    156.         //
    157.         if (AutoSaveInterval.value == 0)
    158.         {
    159.             gameDataClass.AutoSaveInterval = 15; // 15 minutes
    160.         }
    161.  
    162.         if (AutoSaveInterval.value == 1)
    163.         {
    164.             gameDataClass.AutoSaveInterval = 30; // 30 minutes
    165.         }
    166.  
    167.         if (AutoSaveInterval.value == 2)
    168.         {
    169.             gameDataClass.AutoSaveInterval = 60; // 1 hour
    170.         }
    171.  
    172.         // save .dat files
    173.         calendarDataClass.Save();
    174.         studioDataClass.Save();
    175.         gameDataClass.Save();
    176.  
    177.         // Switch to loading screen
    178.         StudioSetupScreen.SetActive(false);
    179.         LoadingScreen.SetActive(true);
    180.  
    181.         // show loading bar
    182.         LoadingBarGO.SetActive(true);
    183.  
    184.         // load new game async [Location 1]
    185.         StartCoroutine(LoadNewGameAsync());
    186.     }
    187.  
    188.     //--------------------------------//
    189.  
    190.     // Main Menu Screen Functions
    191.     private void StartNewGame()
    192.     {
    193.  
    194.         // check if one of the .dat files exists (if one exists; they all exist).
    195.         if (File.Exists(Application.persistentDataPath + "/CalendarData.dat") == true)
    196.         {
    197.             // Inform the player that their previous game data will be deleted.
    198.             // Ask them if they want to continue? [Yes/No]
    199.             ConfirmFileDeleteScreen.SetActive(true);
    200.  
    201.         }
    202.         else
    203.         {
    204.             // no previous save file. Switch to Studio Setup Screen.
    205.             MainMenuScreen.SetActive(false);
    206.             StudioSetupScreen.SetActive(true);
    207.         }
    208.  
    209.  
    210.     }
    211.  
    212.     // Delete previous save files when starting a new game
    213.     private void DeleteFilesNewGame()
    214.     {
    215.         // Delete previous save files.
    216.         File.Delete(Application.persistentDataPath + "/CalendarData.dat");
    217.         File.Delete(Application.persistentDataPath + "/StudioData.dat");
    218.         File.Delete(Application.persistentDataPath + "/FinanceData.dat");
    219.         File.Delete(Application.persistentDataPath + "/GameData.dat");
    220.  
    221.         // switch to Studio Setup Screen.
    222.         ConfirmFileDeleteScreen.SetActive(false);
    223.         MainMenuScreen.SetActive(false);
    224.         StudioSetupScreen.SetActive(true);
    225.     }
    226.  
    227.     // Do not delete previous files. Return player to Main Menu
    228.     private void DeleteBackToMainMenu()
    229.     {
    230.         ConfirmFileDeleteScreen.SetActive(false);
    231.     }
    232.  
    233.     // Load Saved Game
    234.     private void LoadSavedGame()
    235.     {
    236.  
    237.     }
    238.  
    239.     // Settings Screen
    240.     private void OpenSettingsScreen()
    241.     {
    242.         SettingsScreen.SetActive(true);
    243.         MainMenuScreen.SetActive(false);
    244.     }
    245.  
    246.     // Tutorial Screen
    247.     private void OpenTutorialScreen()
    248.     {
    249.  
    250.     }
    251.  
    252.     // Confirm Quit
    253.     private void OpenConfirmQuitScreen()
    254.     {
    255.         QuitConfirmScreen.SetActive(true);
    256.     }
    257.  
    258.     // Return To Menu
    259.     private void CloseQuitConfirmScreen()
    260.     {
    261.         QuitConfirmScreen.SetActive(false);
    262.     }
    263.  
    264.     // Quit Game
    265.     private void QuitApplication()
    266.     {
    267.         Application.Quit();
    268.     }
    269.  
    270.     // Studio Setup Screen Functions
    271.     private void StudioSetupToMainMenu()
    272.     {
    273.         StudioSetupScreen.SetActive(false);
    274.         MainMenuScreen.SetActive(true);
    275.     }
    276.  
    277.     // Settings Screen Functions
    278.     private void SettingsToMainMenu()
    279.     {
    280.         SettingsScreen.SetActive(false);
    281.         MainMenuScreen.SetActive(true);
    282.     }
    283.  
    284.     //----------------------------------------------//
    285.     private void InitializeDataClasses()
    286.     {
    287.         calendarDataClass = CalendarDataGO.GetComponent<CalendarData>();
    288.         studioDataClass = StudioDataGO.GetComponent<StudioData>();
    289.         gameDataClass = GameDataGO.GetComponent<GameData>();
    290.     }
    291.  
    292.  
    293.     //----------------------------------------------//
    294.     private void InitializeMenuButtons()
    295.     {
    296.         // Main Menu Screen Buttons
    297.         Button newGameButton = NewGameButton.GetComponent<Button>();
    298.         newGameButton.onClick.AddListener(StartNewGame);
    299.  
    300.         Button loadGameButton = LoadGameButton.GetComponent<Button>();
    301.         loadGameButton.onClick.AddListener(LoadSavedGame);
    302.  
    303.         Button settingsButton = SettingsButton.GetComponent<Button>();
    304.         SettingsButton.onClick.AddListener(OpenSettingsScreen);
    305.  
    306.         Button tutorialButton = TutorialButton.GetComponent<Button>();
    307.         TutorialButton.onClick.AddListener(OpenTutorialScreen);
    308.  
    309.         Button quitButton = QuitButton.GetComponent<Button>();
    310.         quitButton.onClick.AddListener(OpenConfirmQuitScreen);
    311.  
    312.         // Quit Confirm Screen Buttons
    313.         Button quitConfirmYesButton = QuitConfirmYesButton.GetComponent<Button>();
    314.         quitConfirmYesButton.onClick.AddListener(QuitApplication);
    315.  
    316.         Button quitConfirmNoButton = QuitConfirmNoButton.GetComponent<Button>();
    317.         quitConfirmNoButton.onClick.AddListener(CloseQuitConfirmScreen);
    318.  
    319.         // Settings Screen Buttons
    320.         Button settingsBackButton = SettingsBackButton.GetComponent<Button>();
    321.         settingsBackButton.onClick.AddListener(SettingsToMainMenu);
    322.  
    323.         // Studio Setup Screen Buttons
    324.         Button studioSetupBackButton = StudioSetupBackButton.GetComponent<Button>();
    325.         studioSetupBackButton.onClick.AddListener(StudioSetupToMainMenu);
    326.  
    327.         Button playGameButton = PlayGameButton.GetComponent<Button>();
    328.         playGameButton.onClick.AddListener(PlayNewGame);
    329.  
    330.         // Confirm File Deletion Screen Buttons
    331.         Button proceedYesButton = ProceedYesButton.GetComponent<Button>();
    332.         proceedYesButton.onClick.AddListener(DeleteFilesNewGame);
    333.  
    334.         Button proceedNoButton = ProceedNoButton.GetComponent<Button>();
    335.         proceedNoButton.onClick.AddListener(DeleteBackToMainMenu);
    336.  
    337.     }
    338.     //----------------------------------------------//
    339.  
    340.     private IEnumerator LoadNewGameAsync()
    341.     {
    342.         AsyncOperation async = SceneManager.LoadSceneAsync(1);
    343.  
    344.         while (!async.isDone)
    345.         {
    346.        
    347.             LoadingBar.value = async.progress;
    348.             yield return null;
    349.         }
    350.     }
    351.  
    352. }
    353.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Runtime.Serialization.Formatters.Binary;
    4. using System.IO;
    5.  
    6. public class CalendarData : MonoBehaviour {
    7.  
    8.     public int GameYear;
    9.     public int GameMonth;
    10.     public int GameDay;
    11.  
    12.     private void Start()
    13.     {
    14.         // if CalendarData.dat exists - load it.
    15.         if (File.Exists(Application.persistentDataPath + "/CalendarData.dat") == true)
    16.         {
    17.             Load();
    18.         }
    19.        
    20.     }
    21.  
    22.     // Load Data
    23.     public void Load()
    24.     {
    25.         if(File.Exists(Application.persistentDataPath + "/CalendarData.dat"))
    26.         {
    27.             // create new binary formatter
    28.             BinaryFormatter CalendarDataBF = new BinaryFormatter();
    29.             // open file
    30.             FileStream CalendarDataFS = File.Open(Application.persistentDataPath + "/CalendarData.dat", FileMode.Open);
    31.             // deserialize data
    32.             CalendarDataContainer calendarData = (CalendarDataContainer)CalendarDataBF.Deserialize(CalendarDataFS);
    33.             // close file
    34.             CalendarDataFS.Close();
    35.  
    36.             // data to load
    37.             GameYear = calendarData.GameYear;
    38.             GameMonth = calendarData.GameMonth;
    39.             GameDay = calendarData.GameDay;
    40.         }
    41.     }
    42.  
    43.     // Save Data
    44.     public void Save()
    45.     {
    46.         // create new binary formatter
    47.         BinaryFormatter CalendarDataBF = new BinaryFormatter();
    48.         // create new filestream
    49.         FileStream CalendarDataFS = File.Create(Application.persistentDataPath + "/CalendarData.dat");
    50.         // data to save
    51.         CalendarDataContainer calendarData = new CalendarDataContainer();
    52.         calendarData.GameYear = GameYear;
    53.         calendarData.GameMonth = GameMonth;
    54.         calendarData.GameDay = GameDay;
    55.  
    56.         // serialize data
    57.         CalendarDataBF.Serialize(CalendarDataFS, calendarData);
    58.         // close file
    59.         CalendarDataFS.Close();
    60.     }
    61.  
    62. }
    63.  
    64. // serializable data container class
    65. [Serializable]
    66. class CalendarDataContainer
    67. {
    68.     public int GameYear;
    69.     public int GameMonth;
    70.     public int GameDay;
    71. }
     
    Last edited: May 16, 2018
  18. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    It may help with saving and loading scenes to switch to an 'always load' mentality.

    Basically use your save game system to create a save of your initial state. Then have the new game button load that specific stage. This eliminates the need to have a special case for new game.
     
    HouinKyouma27, frosted, Lu4e and 2 others like this.
  19. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    That's actually very clever, I never thought of doing that.
     
    Kiwasi likes this.
  20. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    @Kiwasi : honestly man, I still like to have a 'New Game' and 'Continue' button on main screen, sometimes I just like to start over lol.
     
  21. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    He's saying that you have the "new game" set up as a save file (that never gets overwritten), so when the user clicks new game it just loads that "starter" file.
     
    Kiwasi likes this.
  22. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    Ohhhh okay, sorry I apologize for the misunderstanding. Been sick past few days so not thinking clearly lol.
     
  23. derf

    derf

    Joined:
    Aug 14, 2011
    Posts:
    356
    Depending on the game itself, some games, like the Resident Evil series for example, would create an "item" manager class that would keep track of all items you picked up in the game and which ones are still available to be picked up.

    When you save the game it simply keeps a reference to the object by tag and a value of 0 or 1 for boolean values. 1 means it was picked up and the game does not render it so it is not available to be picked up again. If it is 0 it is rendered and available to be picked up. This could be reversed and if true would display the pick up item, false would do nothing. In the case of unity the objects enabled value would be stored and applied at game load.

    Its weird realizing that an item is still "there" in the scene on the shelf or on the desk or something, you just can not see it or click on it because you already did. :cool:
     
  24. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    I really wish Unity would cover some more complex topics that show a "start to finish" approach, so I can see Unity's approach to solving these types of problems.

    - There are a lot of official video tutorials that amount to "copy and paste this script" with superficial explanations.

    - Too many videos and tutorials have a chain-like reference structure
    [Video 1] Here is how to do X, for more information about the Y component of X, watch Video 2
    [Video 2] Here is more info about Y. for more info about the B component of Y, watch Video 3
    [Video 3] ... etc
    it all seems so decentralised.

    - Here is how to implement this game mechanic. Don't worry about how it works, why it works, or why we sacrifice a lamb to the great Cthulhu, just know it works and can provide a solution for this particular problem.

    - Here's how to do XYZ, we're going to use GameObject.FindWithTag to find and reference 1000 scene objects... but don't actually use it in a real game because it's too heavy on performance. (then why bother showing us? why not show us the proper way to do it with 1000 objects)

    o_O
     
  25. Deleted User

    Deleted User

    Guest

    My 2 cents on the matter and how I do my saving if could be of any help. There is some interesting material in this thread that I will give a better look at a later time to see if I can make a better system for myself, but anyway thats my approach:

    I don't use serialization/deserialization methods as of enstablished formats with xml json or whatever but I write my own custom binary format trough a simple BinaryWriter.

    Every object that can be saved in my game is derived from a main class called Entity, all the rest is not taken in consideration. I have main Scene manager in the level that has a list of entities in the map, to populate it entities themself will auto subscribe to the list when initialized (I create my levels by external files trough code, so there is nothing to do on Start there for me, I just send things trough a custom Init function).

    What entities does is basicly registering states on themself, states are simpel flags on the current state of the object, for example you picked up the item, the entity sets a state of pickedUp to 1, and that is hte same for the rest.

    One thing I make sure to do is to not save redundant data, for exampel still with the item to pickup, if the item is not interacted with and remain in its default state there is no reason to save any of it, so I have a flag for that only when the object change its default state can be saveable otherwise is totally ignored.

    After all this stuff I simply run a loop trough the list of entities and the ones that can be saved are written in the save file with a string id of the object (usually gameObject name) and the state flag.

    When loading back the game I set a blobal bool isLoading to true so that the entities that are intializing again the scene know that should check for the save file to set their current state. And that is pretty much it, I do have a special case for entities that are dynamic like for example projectiles, since they are pooled object they haven't a fixed id or spot, but if an enemy is shooting a rocket in your face and you decide to quick save before it hits you, need to save that rocket someway, so I have a "special" list for those entities that when the game load back the file know to pick from the object pool to get the entity and reinstantiate it basicly.

    The pros of this method is that I can have a generic modular save data system without beign worried if I introduce new entities. The cons is that with very large maps with way too many entities could be a bit slow to save but not by much, depends on your scope.

    I'm looking into optimizing it by adding a feature that let me skip the scene reloading process, if you load a save file in the same level you are at the moment, is kinda a waste to reload whole scene, but would be better to just apply the new states to the entities, so in theory should have a faster load in same level. This also have a pre requisite that no entity must be destroyed, infact I don't destroy anything, I just deactivate the components that shouldn't be seen in them, still the example with the items to pickup, if you picked it up it just disable renderer and collider for the interaction, the entity component itself stays there awake ready to be changed in state if is needed.
     
    N1warhead likes this.
  26. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    That's certainly an approach I've not even considered and I like the sort of pub/sub relationship you outline. Could you speak more on how you handle, say, an item that has moved since the last game session? Do you split your data into "stuff that has booleans" (an item that has been picked up) and "stuff with vector3 positions" (an enemy that needs to be loaded in that position).

    Yeah the saving has been a concern for me (specially with implementing an auto-save system).
     
  27. ToshoDaimos

    ToshoDaimos

    Joined:
    Jan 30, 2013
    Posts:
    679
    The problem of save/load has nothing to do with Unity. You save/load "game state". You define in your code what game state is and then you define how to serialize/deserialize it. It has completely nothing to do with Unity scenes or game objects. You simply load your save file and create a custom game object hierarchy based on that file.
     
  28. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    That doesn't deal with the issue of dynamic objects that move between game sessions. I'm not sure how you can claim "it has completely nothing to do with Unity scenes or game objects" and then suggest creating a custom game object hierarchy based on the save file?

    by definition, I need to get a GameObjects world/local position and store it as a vector3. Otherwise, when the game is loaded, how is Unity supposed to know where to place these objects?

    as an example, an enemy is spawned at 0, 0, 0 and moves to 10, 0, 0.

    Surely I need to save that vector3, so when the game is loaded Unity can place that enemy back at 10, 0, 0 instead of 0, 0, 0?
     
  29. ToshoDaimos

    ToshoDaimos

    Joined:
    Jan 30, 2013
    Posts:
    679
    In your game state you save some kind of "entity position". It doesn't have to be a Vector3. You can use custom game world units, like for ex. grid position (int, int) for a board game. You would then load that grid position and spawn a game object using a Vector3. Like I said, it has nothing to do with Unity.
     
    winterfluxstudio likes this.
  30. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61
    Ah, I see what you mean. I appreciate the clarification - the topic can be a bit confusing as there are so many different approaches based on what game you're making, the performance requirements, what you're actually saving/loading etc.
     
  31. Deleted User

    Deleted User

    Guest

    The way I retrieve such data for the moment is trough a method I'm not entirely happy about and still exploring for alternative, but for the time beign it works.

    Basicly I retrieve the data to save from entity list in my scene manager. The data is contained in a small container class, which includes also two Vector3 for position and rotation. The container class is more than one, there is a bse one that has only the vectors then for specific type of entities I have inherited classes, for example a door would just to save its open/close state and if is unlocked and stuff like that, while an npc may require a lot more data.

    Can still be generalized if you are confortabl to use System.Reflections which I trid to avoid thats why the sub classes.
    To summarize with some code (just example):

    Base data class
    Code (csharp):
    1.  
    2. public abstract class EntityData
    3. {
    4.     public Vector3 position;
    5.     public Vector3 rotation;
    6. }
    7.  
    Door data class:
    Code (csharp):
    1.  
    2. public sealed class DoorData : EntityData
    3. {
    4.     public int state;
    5.     public bool locked;
    6.     public float whatever;
    7. }
    8.  
    Then onload get the DoorData from the loaded save file and apply the variables on it, then get rid of the save file to free memory after all is done.

    I would just suggest to have a unique manager for all saveable object you got for the reason you can then decide the loading order of things, working with this stuff on Start or Awake is prone to issues.
     
    winterfluxstudio likes this.
  32. winterfluxstudio

    winterfluxstudio

    Joined:
    May 4, 2018
    Posts:
    61

    That's actually an excellent idea I hadn't even considered. Use an abstract class to provide a common definition for some kind of base class that multiple derived classes can share. Might work well for large data structures.
     
  33. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    "Serialisation" just means "getting something in a serial format so it can be written to disk". So if you write your own code to write data to some kind of file then you're doing your own serialisation.

    With regards to security, do you want to stop people reading, writing, or both? Once you've figured that out there are reasonably well known solutions to each. I wouldn't rely on just not using a published data format because if you're doing things in a logical way and someone wants to mess with your data then chances are they can figure out how. I'd look at encryption if you don't want them to read it, and checksums if you don't want them to change it, but there might be better ways these days.

    That's pretty close to my own current solution.

    Objects that need to have persistent data implement an interface, and have a PersistentObject component on their GameObject. On scene load, the PersistentObject looks for any attached instances of the interface, finds the associated data by key, and applies it. On destruction or unload it looks for attached instances, asks for the data they need to store and stores it.

    It hasn't been particularly robustly tested so far, but it seems to be working.
     
    Kiwasi and Ryiah like this.
  34. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,205
    Microsoft defines serialization as a process of converting an object into a stream of bytes with the goal of recreating the object at a later date. Yours is the correct definition of the English word but in programming contexts the one Microsoft uses is the one I've seen most frequently.

    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/serialization/

    Wikipedia has a similar definition.

    https://en.wikipedia.org/wiki/Serialization

    Agreed. For anything important, like leaderboards, I would use some form of server-side storage and vertification.
     
  35. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    That's exactly what I mean by "a serial format so it can be written to disk". My point was that if you're reading from or writing to persistent storage then you're necessarily "searalising" the data whether or not you're using the provided classes to do it, which I presume is what was really being discussed.
     
    Kiwasi likes this.
  36. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    That was my thought too. Taking a class and converting it into linear, savable data is serialization. It doesn't matter if you dump the memory directly, use a binary formatter, use JsonUtility, or convert all of the public fields into Klingon poetry. Admittedly I'm no computer scientist. I just make this stuff up as I go along.

    Which means every saving system has three basic components
    1. Get the data to be saved
    2. Serialize the data
    3. Write the data to long term storage
    Loading is the reverse.
     
    angrypenguin likes this.