How to make a Save & Load System in Unity | 2022

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone my name is trevor and in this video we're going to create a save and load system in unity that can be applied to any type of game as an example we'll be saving and loading a few things from this simple platformer game we'll save and load the amount of times that the player has died the player's position in the scene and finally these specific coins that they've collected we'll be saving the data to a file in both a readable json format as well as an encrypted format which we'll be able to toggle between in the unity inspector and of course the code for everything that we're going to do can be found on github which i'll put a link to in the description of this video how this is going to work is that we'll create a simple c-sharp class to store the state of our game's data then we'll create a singleton class called the data persistence manager which will keep track of the current state of our game data as well as be responsible for orchestrating all of the logic that goes into actually saving and loading the game to do so we'll create an interface called i data persistence and have any other script in our project that needs to save and load data implement that interface when the data persistence manager starts up it'll gather and store references to any script that implements that interface so we can call those scripts appropriately when we save and load the game the next step we need to take is actually persisting that data somewhere outside of the game and in our case that's going to be to a file we'll create a file data handler class that's going to be managed by our data persistence manager and it'll be responsible for converting our game data c-sharp object into a more compressed format and then writing that information to a file this format can be binary xml json plain text or really anything but for this tutorial we'll be going with a json format and of course the file data handler will also be responsible for reading that file and converting the json data back into our game data c-sharp object the process of going from a c-sharp object to a more compressed format is called serialization and the reverse of that is called deserialization you'll hear those terms at least a few more times throughout this tutorial and last as a final touch we'll add the option to encrypt the json data so that it isn't as easily changeable by the player and just to note the main advantage to having the data handler somewhat separated out like this is that if we ever want to change our method of storage for example maybe we need to save to the cloud instead of to a file we just need to write a different data handler and then the rest of our system is mostly unaffected so just to cement how this is going to work let's run through a quick example when we first start the game there won't be any saved data to load in that case we'll want to start a new game which is really simple in this system as we'll just need to create a new game data object from there whenever we want to save the game the data persistence manager will pass that game data object by reference to each script that implements the i data persistence interface those scripts can then modify the game data object and after all the scripts have done so the data persistence manager will use the file data handler to convert the game data to json and then write that data to a file then when we go to load data the data persistence manager will use the file data handler to read in the serialized data and convert it from json back into the c-sharp game data object and after that the data persistence manager will again pass that data to any script that implements the idata persistence interface so those scripts can initialize whatever they need to from that saved data and that's how it's going to work now with all of that said let's get into actually implementing this we'll start with creating our game data for the things that we want to save like i mentioned previously we're going to save the amount of times that the player has died the player's position and these specific coins that they've collected we're going to start off simple though and only save the player's death count just so we can get the initial system working in the scripts directory of the project we'll create a new folder called beta persistence where we'll put all of the scripts relating to this save and load system in there just to keep organized we'll create a new folder called data and then in there we'll create a new c sharp script called game data and then double click it to open it up we can remove these placeholder start and update methods along with mono behavior we'll also need to add system.serializable right above the class definition then we'll create a public int called deathcount to store the amount of times that the player has died next we'll create a public constructor and in that constructor we'll initialize deathcount to be zero the idea here is that when we start a new game whatever we define in this constructor will be our initial values to start with next we'll create the data persistence manager script back in unity under the scripts data persistence folder we'll create a new c-sharp script called data persistence manager and then double-click it to open it up this is going to be a singleton class meaning that we only want one of them in our scene so let's set that up we'll create a public static data persistence manager instance and then this syntax here just means that we'll be able to get the instance publicly however we'll only be able to modify the instance privately within this class then in the awake method we'll initialize our instance and if there already is a data persistence manager in the scene we'll throw an error to let ourselves know that something went wrong since by design there should only be one of these in the scene at any given time so as i mentioned previously this class is going to keep track of the current state of our game's data so we'll add a private game data variable called game data the core of the data persistence manager is going to have three public methods new game load game and save game new game is going to be really simple we just need to initialize our game data to be a new game data object load game will be a bit more complicated first we'll want to load any saved data using the file data handler but we haven't created that yet so we'll just add a to do comment for now second if there was no date of load we'll want to initialize to a new game we can do this by checking if our game data is null and if it is we'll add some logging to let ourselves know that we're initializing to default values and then we can just call our new game method and third we'll want to push that loaded data to all other scripts that need it but we haven't set that up yet so we'll just add a to-do comment for that as well and last for save game we'll first want to pass the data to other scripts so they can update it and second we'll save that data to a file using the file beta handler for now we'll just add to do comments for both of these things as for when these methods are called it depends on how you want to load and save your game this class is set up as a singleton instance and these methods are public so that they can be called from other scripts for example from a main menu a pause menu or really anywhere else that you need to save and load data from with that said for this tutorial we're going to keep things simple we'll load the game on startup and then save the game when the application exits to do so we'll create a start method and then call load game from there so that the game is loaded on startup and then we'll also create a method called on application quit which gets called anytime the game exits and then we'll call save game from there the next step we'll take is setting up the system to call other scripts when we save and load the game back in unity we'll create a new c-sharp script called i data persistence and double-click it to open it up this is going to be an interface or in other words it's just going to be used to describe the methods that the implementing script needs to have we can remove these placeholder methods remove mono behavior and instead of this being a class we'll change it to be an interface then we'll declare a method called load data that takes in a game data object called data and another method called save data that takes in a reference to a game data object called data the reason we want to pass by reference here is that when we save data we actually want to allow the implementing script to modify the data whereas when we load data the implementing script only cares about reading that data in this sample project i have a script that's attached to the ui text called death count text which is also where i'm keeping track of how many times the player has died if we take a quick look through the script we'll see that there's a death count variable at the top that we're incrementing every time the player dies and then we're updating the actual ui text in an update method to whatever the value of that variable is to allow this script to save and load data we'll implement the i data persistence interface by adding it after mono behavior at the top of this script then we'll add the methods our interface defines load data and save data when we load data we'll just set the death count in this class to the death count in our game data in the save data method we'll do the opposite and modify our game data death count by setting it equal to the death count in this class back in the data persistence manager script we need to write some code to get a reference to every script in our scene that implements the i data persistence interface and then call them appropriately in the save game and load game methods at the very top of this script we'll add a using statement for system.link which is going to give us some nicer syntax for finding the idate of resistance objects then we'll create a new list of type i data persistence called data persistence objects in our start method above the load game call we'll say this dot beta persistence objects equals find all beta persistence objects which is a method that we're going to create towards the bottom of this script we'll create a private method that returns a list of type i data persistence called find all data persistence objects because we're using system.link we can find all scripts that implement the i data persistence interface in our scene using this line of code here but just keep in mind that those scripts also need to extend from mono behavior to be found in this way then we can return a new list and pass in the result of that call to initialize the list now in the load game method we'll loop through each of the data persistence objects in our list and then call load data passing them the game data likewise in save game we'll do the same thing but call save data and then pass the game data by reference and just for now we'll also add some logging statements to print out the death count when we load and save just to make sure this is working so far back in unity let's set this up and make sure things are working as expected in the scene hierarchy we'll create a new game object called data persistence manager and then we'll drag on the data persistence manager script to add it after that we can hit play to enter play mode if we take a look at the console logging we'll see that no data was found and the death count was initialized to zero which is correct according to how we have everything set up if we die a few times so our death count is 3 and then we close the game we'll see that upon exiting the game we get some logging indicating that our death count is 3 which is also correct of course if we try to play this again right now the death count will be initialized to zero again because we're not storing the data to a file yet so let's do that next in unity we'll create a new c-sharp script called file data handler and then double-click it to open it up we can remove these placeholder methods as well as mono behavior since we want our data persistence manager to be the one managing this we'll also add using statements for system and system.io then we'll create a couple of private string variables one called beta der path which is going to be the directory path of where we want to save the data on our computer and another called data file name which will be the name of the file that we want to save to we can then create a public constructor to take in these two values and set them accordingly we'll then create a public method called load which will return a game data object and another public method called save which will take in a game data object we'll implement the save method first to save data will first need a full path which includes the file name we could use string concatenation like so to create this but since different operating systems have different file separators a better way to do this is to use path dot combine passing in the directory path and the file name to combine the two then we'll do a try catch block here so that way if an error occurs when we're writing data to the file we can catch that and then log an error to let ourselves know what happened before we do anything else we should create the directory path just in case it doesn't exist yet on our computer we can do this with the line directory dot create directory and then pass in the directory path that we want to create you could just use the data der path variable but another way to do this since we're dealing with a full path at this point is to say path dot get directory name and then pass in the full path to that next we want to serialize our game data object into a json string we'll create a new string called data to store and then set that equal to jsonutility.2json and then pass in our data optionally we can format the json data by passing in true as an optional second parameter so we'll do that as well and finally we'll actually write the file to our file system the syntax for this can be a little strange but when dealing with reading or writing to a file it's best to use using blocks as they ensure that the connection to that file is closed once we're done reading or writing to it so we'll say using and then we'll create a new file stream passing in the full path and then specifying file mode dot create since we want to write to a file then inside of that using block we'll do another using statement and create a streamwriter passing in the stream that we just created and then finally we can do writer.write and then pass in the data that we want to write to that file that's good for the save method but now let's fill out the load method which is going to read that file and then parse it back into our game data object just like with the save method we'll want to work with a full path that includes the file name so we'll copy and paste that part over then we'll declare a game data object called loadeddata which is the variable that we're going to load into before we try to load data we should check if the file we're going to load from actually exists then we'll return our loaded data at the bottom of this method meaning that if the data exists we'll load it but if it doesn't exist we'll just be returning null and before we actually try to read from the file we'll do a try catch block just like we did in the save method for reading in the file we'll declare a string called data to load then we'll add a using statement and create a file stream passing in the full path and then specifying filemode.open since we want to read from the file and then another using statement where we'll create a stream reader and then pass in the stream that we just created and finally we can use reader.readton to load the file's text into the data to load variable as a string so that will load the serialized data but now we need to deserialize it from json back into our game data object we can do this by calling json utility dot from json specifying the game data type passing in the serialized data and then setting the result of that to our loaded data variable and that'll do for the load method so now let's hook all of this up at the top of the data persistence manager script we'll add a serialized field variable for the file name that we want to save our data to we'll also declare a file data handler variable called data handler then in the start method we'll set the data handler to be a new file data handler and then pass in application.persistentdatapath for the directory and then the file name variable for the file name application.persistentdatapath will give the operating system standard directory for persisting data in a unity project if you want to save your data somewhere else on your machine you can change this but unless you have a good reason to then sticking with application.persistent datapath is usually a good choice i'll put a link in the description of this video to the unity documentation that you can use to see where this points for your operating system at the top of load game in the data persistence manager we'll set our game data equal to data handler.load and take note that when the save data doesn't exist the result of this method will be null in which case with how this is currently set up we'll create a new game then at the bottom of the save game method we'll call datahandler.save and then pass in the gamedata to be saved and we can also remove these log statements since at this point we should actually see the data persist to a file back in unity in the inspector for the data persistence manager script we'll need to pick a file name to save to this can be whatever you want and have whatever file extension you want or no file extension if you prefer since we're saving the data as json you could give it a json extension however i'm going to call mine data.game now if we enter play mode you'll see that our death count starts at zero so we'll die a few times and then exit play mode so the game saves now if we go to where the application.persistent datapath is for your operating system you should see a file with the name you specified we can open this up in a text editor to see that our data was saved to this file in a json format successfully and back in unity if we go back into play mode our death count now initializes as the loaded value this shows that the core of our save and load system is working as intended next let's also store the player's position so that they start in the same place where they leave off in our game data script we'll add a vector3 variable called playerposition and then in the constructor we'll just initialize this to vector3.0 but remember that these should correspond to the initial values you want for a new game in this sample project i have a player game object that has a character controller 2d script attached that would be a good spot to save and load the player's position so we'll open up that script and then implement the i data persistence interface and then we'll add the load data and save data methods in load data we'll set the transformed opposition of this game object to the data.player position of our game data and of course in save data we'll do the opposite setting the data dot player position equal to the position of this game object and just like that now we're saving the player's position we can go back into play mode move the player to a different spot in the scene and then exit play mode so that the game saves if we look at the data file we'll see that we're now successfully saving the player's positional values and of course if we go back into play mode the player now starts in the same position as where we left off the next thing we want to save are the coins that the player has collected this is a bit more complicated than the previous things we've saved so let's think about it for a second we have a bunch of coins scattered throughout the level and when we collect them they disappear when we save the game we need to know specifically which coins we've collected so that when we load the game we know exactly which coins to make disappear so they can't be collected again we can do this by saving the data as a dictionary where the key is a string that represents a unique id for each coin and the value is a boolean on whether or not that coin has been collected this concept works well not only for collectibles but also works well for things like checkpoints unlockables and more in our game data script we'll add a dictionary of type string to boolean called coins collected and then in the constructor we'll just initialize this to an empty dictionary next let's give each coin a unique id in this sample project each coin game object has a coin script attached which will be a good place to put the id at the top of that script we'll add a serialized field private string id we could fill this out manually but through researching some stuff for this tutorial i actually found a really cool way of generating unique ids automatically we'll create a context menu option called generate guide for id and then right underneath that we'll create a private void method called generateguid in that method we'll simply set our id variable equal to system.guide.newgoid.2string now back in the unity inspector we can right click on our coin script and then select the generate guide for id option that we created which will fill out the id field for us for those unfamiliar with goods they're essentially strings of 32 characters that have an extremely high probability of being unique so we'll go through and generate these for each coin so that every coin has a unique id next back in the coin script we'll implement the i data persistence interface and add save data and load data methods as usual for each coin this script already has a boolean called collected that's keeping track of if the coin has been collected or not so we can use that as the value to store in the dictionary in load data we'll call try get value on the coins collected dictionary using the id to get whether or not this coin has been collected and if it has been collected all we need to do in this case is set the visual game object to be inactive so the coin can't be collected again when we save the data we'll check if the id for this coin is already contained in the coins collected dictionary and if it is we'll remove it before trying to add it again and when we add it we'll add the id as the key and then the collected boolean as the value so that will take care of saving and loading the data for the coins themselves but there's also this ui text that needs to be loaded there's a script called coins collected text attached to the ui text that we can use to load this in that script there's a variable called coins collected which is essentially what's getting displayed in the ui so all we have to do here is implement the i data persistence interface as usual and also implement the save and load methods but in this case we don't actually need to save anything as for loading we'll just loop through each key value pair in the dictionary and then increment coins collected for each value that's true which in this case will put our coins collected variable at the correct amount so you would think that this should work but if we jump back into play mode collect some coins and then stop the game and look at our data we'll see that the dictionary isn't being serialized and saved to our file at all what's happening here is that the json utility class that we're using for serialization unfortunately doesn't support more complex data types like dictionaries there's a couple of different things we could do to get around this the first would be to use a different method of serializing our data for json data there's a third party library called json.net that's free on the unity asset store as of making this video which i haven't used myself but i have heard good things about the second option is to use the i serialization callback receiver interface which we can use to put our dictionary into a format that can be saved by the json utility in this tutorial we're going to take the second approach however if you find yourself having to save a lot of complex data types in your game then something like json.net could be worth checking out back in unity under these scripts data persistence folder will create a new folder called serializable types and then in there we'll create a new c-sharp script called serializable dictionary and then we'll double-click it to open it up we can remove the placeholder methods and mono behavior and then we'll also add system dot serializable to the top of the class then we'll add a generic key and value to the class name like so and we'll also extend from the standard dictionary class so that we inherit all of the capabilities of a standard c-sharp dictionary and then we'll also implement an interface called eye serialization callback receiver and with that is going to come two methods on before serialize and on after d serialize as you probably guessed these methods will get called right before serializing and right after deserializing which means we can add translations in each of these to go to and from the dictionary type next we'll add two serialize field private lists one for keys and another for values and we'll initialize them to new empty lists in the on before serialize method we'll make sure that these lists are cleared and then we'll loop through each key value pair in this dictionary and then add those to the lists in the on after the serialize method we'll first make sure that the dictionary is cleared then we can simply loop through and add each key value pair to the dictionary and just for good measure we can add a defensive programming check here to ensure that the key and value lists are the same size since if they aren't then something went horribly wrong and we should let ourselves know and finally back in our game data class all we need to do is switch out the dictionary for the serializable dictionary class that we just created now if we play this again and collect some coins and then exit play mode and look at our data we'll see that these two lists are now getting saved to the file as expected and of course if we enter play mode again we'll see that the data was loaded successfully and that the coins we collected were persisted so that pretty much does it for saving all of the data that we wanted to but you'll notice that because we're saving data in a readable json format it would be really easy for the player to open up this file and then modify the game's data which might not be desired if you've done a google search on this or you've seen another tutorial on creating a save and load system it's likely you've come across something called a binary formatter which we could use to save the data as binary instead of json while this would work it's actually highly recommended by microsoft to not use the binary formatter and if you are using it to switch to an alternative so for this tutorial we're actually going to encrypt our data using a simple encryption implementation called xor encryption which i know might sound like a scary thing but i promise that it's actually going to be really easy i'm not going to dive very deep into the details of how xor encryption works but essentially we're taking each character of our json data and doing an xor operation against another character to flip that character into a different one because of how xor operations work we can do the exact same thing to decrypt the data back to the original characters for that reason we'll want to keep track of the characters we're encrypting the data with so that we can decrypt it in the same way and for that we'll create a string which will act as our encryption code word we'll essentially pick characters from that code word to encrypt and decrypt against which means that the only way to decrypt the data will be to know both the codeword and the encryption algorithm being used but just to be clear this type of encryption or any type of encryption that's handled completely on the client side can be broken somewhat easily since your code word and the algorithm would be stored on the client side in some fashion with that said the encryption we're going to do will make it quite a bit tougher to alter the game's data and will likely prevent your average user from doing so with all of that said let's actually implement this in the file data handler we'll add a boolean variable called use encryption and set it to false we'll also create a private read-only string called encryption codeword you can make this whatever you want but i'm just going to use the string word then in the constructor we'll take in another parameter for whether or not we should use encryption and then set that accordingly and then at the very bottom of this class we'll create a private method called encrypt decrypt that will take in some string data and then return a string of the encrypted or decrypted data in that method we'll loop through the entire data string and for each character we'll do an xor operation between the original data and an index in the encryption code word and then after that we'll simply return the encrypted or decrypted data now in the save method if we're configured to use encryption we'll encrypt the data before we write it to the file and then we'll do the same thing in the load method but decrypt it after we've read it from the file next in the data persistence manager script we'll add a serialized field boolean for whether or not to use encryption and then we'll make sure to pass that into our file data handler when we create it in the start method since we're switching formats we should delete the data file on our computer so that we're starting from a clean slate and back in unity we'll check this box to use encryption on our data persistence manager script and now if we play this and then do some stuff and stop play mode and then go look at that file we'll see that the data is encrypted and no longer easily readable or editable and of course if we start up the game again the game loads based on our save data just like before and that's it thank you so much for watching if you liked the video please give it a thumbs up so more people see it and if you want to see more from me go ahead and hit the subscribe button as well i've got a couple of ideas for other tutorials that might branch off of this one so if there's something more you want to see out of a save and load system feel free to put a comment below or you could stop by my discord server which is a great place to ask me questions suggest a video topic or just show off the game that you're creating you can also follow me on twitter or instagram where i mostly post about the game that i'm creating anyways thanks again for watching and most of all i hope this was helpful [Music] you
Info
Channel: Shaped by Rain Studios
Views: 138,541
Rating: undefined out of 5
Keywords: trevermock, trevormock, trevor, trever, mock, unity save and load, unity save system, unity save game data, unity save, how to make a saave and load system, how to make a save and load system in unity, unity save system 2022, unity save game data 2022, unity save & load, unity save load, unity save load json, unity save to file, unity save load file, unity data persistence, unity persistent data, save and load system in unity, unity encrypt save file, unity encrypt json
Id: aUi9aijvpgs
Channel Id: undefined
Length: 26min 55sec (1615 seconds)
Published: Wed Mar 09 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.