banner



Load Saved Game Ui Design

Search Unity

Unity ID

A Unity ID allows you to buy and/or subscribe to Unity products and services, shop in the Asset Store and participate in the Unity community.

  1. This isn't a question about how to serialize, more about the design of a saving and loading system.

    I'm starting to have second thoughts about my save system. I'm currently on the path to making Method A, as written below, but I'm not sure if it is the right choice. I would greatly appreciate any discussion or advice (code will most likely not be necessary, since this is more about the design of the system)

    I think I've narrowed the designs down to two different ones, that both have their own pros and cons.

    [Method A]
    Call the load function on each individual class when I need it. This requires me to code a parsing system for a file, that searches for the specified "key", then loads the bytes from that key to the saving/loading system.

    Pros:
    -Save and load data when I need it
    -Low memory cost (only what is needed is loaded)

    Cons:
    -Parsing through a file is probably slow, especially if the file is large
    -Most likely will require TWO save files. During runtime, all load/save functions refer to a temporary file. When the player saves the game at a checkpoint, this file will be copied to the permanent file. When the player loads the game, the temporary file is copied to the permanent file. When the game is exited, the temporary file is deleted

    [Method B]
    Have a "GameState" class that holds all the smaller classes that will be saved. This is the class that is loaded and saved to the file. All other classes refer to this class when loading/saving. When saving at a checkpoint, this class will write to a file.

    Pros:
    -No having to parse through the file
    -Only a single save file (no temp/permanent) since all classes can refer to this one class for their own data

    Cons:
    -Because of the large class size, it may take a while to save/load
    -ALL save data for the game is loaded at startup, and would stay in memory. This could potentially eat up a large chunk of the memory (but I'm not really sure how much)

    I'm not really sure which method to go with, so any input is greatly appreciated.

  2. Hey there,

    Me and my colleague have encountered this issue in the past and we opted for a pattern known as Memento. The following link gives an accurate description of how the pattern works - http://www.oodesign.com/memento-pattern.html. Give it a try... if you encounter any problems, let me know.

    Regards,
    Clayton

  3. Go with the second, simpler option, then if you actually run into memory problems look to optimize.
  4. Thanks for the responses!

    That memento thing seems similar to Method B that I listed.

    Well, I mean, there's a lot of data that the second option would hold in memory.
    All of the player data (8 ints, 2 lists, but that will be increasing)
    All of the player's house data (a few lists)
    Data for each scene in the game (at least 20 scenes * 4 lists, a few ints and bools)
    NPC Data (At least 30 NPCs * a few ints)
    Randomly generated areas (About 7 of these * 1 large array, 4 lists, a few ints and bools)

    This is just off the top of my head, but that seems like a lot of data to hold in memory constantly, is it not?
    I'm just trying to think everything through first, so I don't have to come back and reprogram half my game later.

  5. From the list you just gave, I seriously doubt you will exceed a meg of memory. But it really depends on "data for each scene" and the length of your lists. If you're thinking that stuff like "8 ints" is even relevant to the scale of your save file, then you're miles away from needing optimization.

    My guess is that putting together a container that maintains all relevant gamestate will end up being useful outside of your save system. So you probably will get extra mileage out of it anyway.

    Finally, if you have a container like that and you end up realizing that you do need to selectively load - then making the gamestate do lazy loads will be a pretty natural extension.

    That said, I would make the effort to put together a keying structure for your data. Being able to uniquely identify data and fetch it from a consistent location (without digging through hierarchies) is almost always worthwhile (if nothing else, for debugging and diagnostics).

  6. Sorry, you used a bit of terminology that I don't know..
    What is a "lazy load"?
    Also, what is a "keying structure"?

    The way I planned on doing it was writing a save and load function for each object, that will load specified variables off the GameState manager, like so:

    1. public void PlayerLoad( ) {
    2.     health = gameManager. PlayerData . health ;
    3.     stamina = gameManager. PlayerData . stamina ;
    4. //Continue like his for every variable that needs to be loaded
    5. public void PlayerSave( ) {
    6.     gameManager. PlayerData . health = health;
    7.     gameManager. PlayerData . stamina = stamina;
    8. //Continue like his for every variable that needs to be saved
    9. //Have all variables here
    The GameManager class would just hold a new PlayerData (or PlayerData loaded from a file) along with any other data classes.
    Then, I call load or save on whatever object I want to be loaded or saved to get the data from there. When I want to actually save the game, I just save the GameManager class.
    Last edited: Mar 28, 2014
  7. lazy load - Load data as needed, instead of up-front.

    keying structure - a unique way to identify data based on having unique strings used as IDs for each piece of data.

  8. "Lazy loading" is just waiting to load something till someone requests it.
    By "keying structure" I just meant have a unique id on every meaningful object in your game.

    So something like:

    1. public CharacterData GetCharacterData( string character_id ) { ... }
    This serves two purposes, one - you can do the simpler "save/load everything in one pass" implementation first, then if you need to switch to something where you selectively load data, you can just modify that function (no change to the code that calls the function).

    Two, if for some reason you need to query something in your game, you always know where to find it. So if you have a quest to keep some character alive, it's always easy to get a reference to the character by id without needing to search around gameobjects or object hierarchies. (even if this kind of thing doesn't end up being relevant for your actual gameplay, it will probably end up very useful while debugging)

    EDIT:
    Also, I've noticed that in a lot of your code you're manually copying fields all over the place:
    data.player.health = arg.health
    etc. It's a good idea to keep that kind of thing to a minimum, if you end up needing to copy a specific class a lot for some reason make a "copy" or "clone" method. It'll just save you headache later if you need to add or change fields (you just make the change in one place - that method).

    Last edited: Mar 28, 2014
  9. Thanks for the responses, guys.

    That key structure makes sense. That would work well for my areas, since I then can search by area name/id to return the correct area data.
    (I assume the function just loops through an array of data until it finds the string specified, then returns the data at that position?)

    However, I don't think this would be able to switch over to a "lazy load" system would it? Because the gamestate class will need to load from a file at startup, and as far as I know, there's no way to load just one variable from the file?

    Also, is there anyway to make the "GameState" class accessible to everything so that I don't have to search for a specific instance of it? static, maybe?
    Just so that I don't have to do Find or something whenever I enter a new scene.

  10. However, I don't think this would be able to switch over to a "lazy load" system would it? Because the gamestate class will need to load from a file at startup, and as far as I know, there's no way to load just one variable from the file?

    Here's the idea...

    1.   Dictionary< string, CharacterData> CharactersInMemory;
    2. public CharacterData GetCharacter( string key ) {
    3. if ( CharactersInMemory. TryGetValue ( key, out c ) ) return c;
    4. var newly_loaded = LoadCharacterFile( key + ".save" )
    5.       charactersinmemory. add ( newly_loaded. key )
    That's an rough pseudo code example of a "lazy load". If at first you are saving everything into a single file, then you don't need the "else" clause there - since you're guaranteed to have already loaded everything (so attempting to get something that isn't in that dictionary means you have a bug somewhere).

    The nice thing is that you can write it first using simple assumptions -> "everything will be there all the time", but then if you need to optimize stuff out it's pretty natural (add the else, selectively load new data).

    You'll also notice that because you have a string based unique key for something, you can use that as the name of the file as well - this is a (rough) example of "code by convention" that often saves a lot of time duplicating names and stuff.

    As for some like "making it accessible to everything" you start getting into all kinds of other stuff. The easiest way is just make it static "StateManager" or "Game" or whatever, people generally use singletons for this. Personally, I prefer using a Dependency Injection container for this stuff, but that depends on your comfort levels. Here's a nice article on DI stuff: http://blog.sebaslab.com/ioc-container-for-unity3d-part-1/. I would take some of the guys "this is bad" stuff with a grain of salt as I think he likes to over-engineer, but he still lays out the ideas well.

    Last edited: Mar 28, 2014
  11. hmm, the problem with that is that I would have a different file for each data I wanted to save. I'm trying to keep everything in one save file to allow for easy save backups / transfers and keeping the possibility open for Steam Cloud.
    That's why I mentioned having a savestate class that holds ALL data. So that it can save it all into one file.

    (Sorry, I should have mentioned before that I wanted only one save file)

  12. Honestly, you're making this way harder than it needs to be. Your original post talks about splitting up data reads...if you're thinking about always storing it in a single file, but doing some kind of random access data read as a way to save memory consumption, that's self defeating because you're going to need to load the entire file into memory in order to do a random access read. Worse, you'd probably be using xml or something, so you'd also be dealing with character data and another layer of conversion.

    A common way to solve the "deployment" problem is to use a compressed file, then unzip or decompress, giving you access to all the data.

    But honestly, just load it all into memory... you're dealing with trivial amounts of data.

  13. I do have a tendency to over-complicate things, sorry.

    I also should have been more clear with what I was trying to accomplish, which is trying to figure out the most optimal way for having a single save file.
    I guess I can just, like you said, load all data into memory.
    I'm just worried it will screw me over in the end. For example, you have something like Skyrim, which has a single save file. I assume that they access specific sections of the file, and only load those sections when needed, no?

    Obviously my game isn't anywhere near the same level as Skyrim, but I have a lot of the same things that need to be loaded/saved (Objects in an area so you know what objects are dropped in what location, NPC data, your character data, lists of items in chests, etc, etc)

  14. Fells,
    Most likely skyrim loads all relevant gamestate into memory.

    Also, the above code will work for a single file or many files or something entirely different. It doesn't really matter... the point is that you can -change your mind later-.

  15. Alright, I'll give it a try.

    Thanks a lot for all the responses, and putting up with my (probably) stupid questions.

  16. For more complicated games like RPG's that start to have a lot of references to unique ID's and a lot of NPC states, location of items, customized inventory items, etc, I can't help but wonder if a lightweight database like SQLite would be appropriate. I've never tried that myself in a game, so I don't know what the pros and cons would be. My impression is that SQLite in particular is heaviliy optimized for fast updates and retrievals of random access data that happens to be sitting in a single file.

    In some cases, you might be able to get away with just saving what's changed, or at least only what's relevant to a specific game save. So at the least, you could leave options or other global settings and other game saves alone when saving the game.

    But like other posters have mentioned, the memory usage part of it seems like a non-issue either way.

  17. I like you also tend to overcomplicate, since I need to constantly be on guard for places where I over complicate matters, I tend to be especially hard on cases where people are making an issue harder than it needs to be (since I need to guard for it in my own work).

    Anyway, good luck. Don't worry about optimizations or perfection, chances are you're at an early phase of your project - once you write a pass and actually measure the results, you'll be in a much better position to judge your future needs or the inadequacy of a given solution.

    Perhaps the most important thing when working with new code, or code in a new domain is not being afraid to fail. Chances are the code you (or anyone) new to a problem will write is going to suck and be S***ty. It's almost unavoidable. The key is not to avoid failing, the key is to fail quickly, so you can learn the lessons you need to.

  18. Well, a complicated RPG is exactly what my game is; think Skyrim + Harvest Moon + Legend of Zelda
    It gets pretty complicated pretty fast.

    I have never used SQL in any form, but it seems to be a very popular..format? I see it around a lot.
    I think what tends to complicate the system even more in my case is that I essentially need two sets of data: The data of the last save, and the current data. The game needs to use all the current data for all of its calculations and gameplay, but if the game crashes, or the player quits without saving, it needs to revert to the last saved data.

    The method of just loading everything up into memory accomplishes this pretty easily, since the "current data" is everything in memory, and the "save data" is everything in the file.

    Yeah, that's some really solid advice.

    A lot of this is new territory for me, and my tendency to overcomplicate, plus wanting it to be perfect from the start tends to slow my progress down a lot.

    Last edited: Mar 28, 2014
  19. So, just to double check that I'm on the right path, and am not doing stupid stuff, here is my code to currently save the player:

    StateManager:
    (This keeps a GameState class that has all the data for the game)

    1. using System.Collections ;
    2. using System.Xml.Serialization ;
    3. using System.Collections.Generic ;
    4. public static class StateManager {
    5. public static GameState gameState = new GameState( ) ;
    6. //The holder class to save/load from
    7. public PlayerData playerData = new PlayerData( ) ;
    8. public static void SaveGame( ) {
    9.         SaveManager. Save (gameState, Application. persistentDataPath + "SaveGame.nov" ) ;
    10. public static void LoadGame( ) {
    11.         SaveManager. Load ( ref gameState, Application. persistentDataPath + "SaveGame.nov" ) ;
    12. //Containers for saving data
    13. [XmlArray( "ItemInventory" ),XmlArrayItem( "Item" ) ]
    14. public List<ItemInvData> itemInvData = new List<ItemInvData> ( ) ;
    15. [XmlArray( "ToolInventory" ),XmlArrayItem( "Tool" ) ]
    16. public List<ToolInvData> toolInvData = new List<ToolInvData> ( ) ;
    17. //Item Inventory Save Class
    18. public class ItemInvData{
    19. //Tool Inventory Save Class
    20. public class ToolInvData{
    And the functions for saving and loading my player (located on a script attached to my player)
    1. public void SavePlayer( ) {
    2.         Debug. Log ( "Saving Player" ) ;
    3. //Get the reference to the player inventory
    4.         scr_PlayerInventory invRef = GetComponent<scr_PlayerInventory> ( ) ;
    5. //Get the StateManager's player data
    6.         PlayerData dataToSave = StateManager. gameState . playerData ;
    7. //Overwrite the values in the StateManager's data
    8. //************ Items *************
    9. //Loop through all slots of the inventory
    10. for ( int i = 0 ; i < invRef. itemInv . Count ; i++ ) {
    11. //Create a blank item in the slot
    12.             dataToSave. itemInvData . Add ( new ItemInvData( ) ) ;
    13. //If the slot is not null, copy over the important info
    14. if (invRef. itemInv [i] != null ) {
    15.                 dataToSave. itemInvData [i] . assetName = invRef. itemInv [i] . originalData . name ;
    16.                 dataToSave. itemInvData [i] . stack = invRef. itemInv [i] . stack ;
    17. //Otherwise, fill it with blank values
    18.                 dataToSave. itemInvData [i] . assetName = "" ;
    19.                 dataToSave. itemInvData [i] . stack = 0 ;
    20. //************ Tools **************
    21. //Loop through all slots of the inventory
    22. for ( int i = 0 ; i < invRef. toolInv . Count ; i++ ) {
    23. //Create a blank item in the slot
    24.             dataToSave. toolInvData . Add ( new ToolInvData( ) ) ;
    25. //If the slot is not null, copy over the important info
    26. if (invRef. toolInv [i] != null ) {
    27.                 dataToSave. toolInvData [i] . assetName = invRef. toolInv [i] . originalData . name ;
    28.                 dataToSave. toolInvData [i] . stack = invRef. toolInv [i] . stack ;
    29. //Otherwise, fill it with blank values
    30.                 dataToSave. toolInvData [i] . assetName = "" ;
    31.                 dataToSave. toolInvData [i] . stack = 0 ;
    32. //************ Stats **************
    33. //Get the stats component
    34.         scr_PlayerStats playerStats = GetComponent<scr_PlayerStats> ( ) ;
    35.         dataToSave. health = playerStats. health ;
    36.         dataToSave. maxHealth = playerStats. maxHealth ;
    37.         dataToSave. stamina = playerStats. stamina ;
    38.         dataToSave. maxStamina = playerStats. maxStamina ;
    39.         dataToSave. exp = playerStats. exp ;
    40.         dataToSave. expToLevel = playerStats. expToLevel ;
    41. public void LoadPlayer( ) {
    42.         Debug. Log ( "Loading Player" ) ;
    43.         PlayerData loadedData = StateManager. gameState . playerData ;
    44. //Get the reference to the player inventory
    45.         scr_PlayerInventory invRef = GetComponent<scr_PlayerInventory> ( ) ;
    46. //************ Items *************
    47. //Loop through all the slots in the item list, copying it over to the player's inventory
    48. for ( int i = 0 ; i < invRef. itemInv . Count ; i++ ) {
    49. if (loadedData. itemInvData [i] . assetName != "" ) {
    50.                 ItemData loadedItem = Resources. Load <ItemData> ( "Items/" + loadedData. itemInvData [i] . assetName ) ;
    51.                 Debug. Log ( "Loading Item At: " + "Items/" + loadedData. itemInvData [i] . assetName + ".asset" ) ;
    52. //Copy the original asset to the inventory
    53.                     loadedItem. CopyToInventory (invRef, i, false ) ;
    54. //Overwrite the instance data
    55.                     invRef. itemInv [i] . stack = loadedData. itemInvData [i] . stack ;
    56.                     Debug. Log ( "No Item Found!" ) ;
    57. //************ Tools **************
    58. //Loop through all the slots in the tool list, copying it over to the player's inventory
    59. for ( int i = 0 ; i < invRef. toolInv . Count ; i++ ) {
    60. if (loadedData. toolInvData [i] . assetName != "" ) {
    61.                 ToolBaseData loadedItem = Resources. Load <ToolBaseData> ( "Tools/" + loadedData. toolInvData [i] . assetName ) ;
    62.                 Debug. Log ( "Loading Item At: " + "Tools/" + loadedData. toolInvData [i] . assetName + ".asset" ) ;
    63. //Copy the original asset to the inventory
    64.                     loadedItem. CopyToInventory (invRef, i, false ) ;
    65. //Overwrite the instance data
    66.                     invRef. itemInv [i] . stack = loadedData. toolInvData [i] . stack ;
    67.                     Debug. Log ( "No Item Found!" ) ;
    68. //************ Stats **************
    69. //Get the stats component
    70.         scr_PlayerStats playerStats = GetComponent<scr_PlayerStats> ( ) ;
    71.         playerStats. health = loadedData. health ;
    72.         playerStats. maxHealth = loadedData. maxHealth ;
    73.         playerStats. stamina = loadedData. stamina ;
    74.         playerStats. maxStamina = loadedData. maxStamina ;
    75.         playerStats. exp = loadedData. exp ;
    76.         playerStats. expToLevel = loadedData. expToLevel ;
    So essentially the gameState class gets all data from the file. Any object that needs to load or save does it to the gameState class.
    What do you think?
    Last edited: Mar 28, 2014
  20. I think it looks good. But that doesn't count for much.

    You'll find out if the code is good or not by how well it ends up working for you and how you end up changing it. You might, for instance, extend it to save some NPCs, in which case you'll probably want to refactor. But maybe your game doesn't need npcs saved, ever, in which case making "character save" instead of "player save" would have been a waste of time.

    Unless you're dealing with strict algos and performance on a known problem, it's sort of hard to say if code is 'good' or not on its own. It's more about how it fits into the rest of your codebase and how it solves the problems you need solved.

  21. Well, this is just the base for the gameState class. I'll need to add more for sure.
    I'll make a separate NPC save, since the values I'm saving are very different from the player. I'll also add a dictionary for the areas and such.

    Essentially the gameState class has lots of smaller holder classes for each type of object. Everytime I want to add another object to be saved, I make a holder class in the gameState, and load and save functions on the object. It takes a bit of work, but it gives me a decent amount of freedom.

Load Saved Game Ui Design

Source: https://forum.unity.com/threads/best-design-for-a-saving-loading-system.236907/

Posted by: stokeswharroposs.blogspot.com

0 Response to "Load Saved Game Ui Design"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel