Unity Save Game System Tutorial | Save Data To JSON

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome to today's video in today's video i'm gonna be going over how we could make a safe load system uh using json within unity the reason i'm using json and not something like the binary i'm not using something like the uh binary formatter is because microsoft actually recommends not using it they say it's not safe and it should just not be used now for your game maybe that's not a big issue you know it only becomes an issue if you're sharing save files but if your game gets big and players want to upload their save files on you know steam workshop or just on different sharing sites there's no way to make those save files safe or check whether they're safe so someone could upload a dodgy save file a player would unknowingly download it and then that could execute some code on their machine so the binary format is out so i'm using json you could use something like xml if you wanted there are ways to encrypt it um our current save when you look at a json file it's fully readable here players can access this file and then they can change the data in it so they could do uh you know the x position of the player is 20.22222 so it's not safe in that regards players will be able to modify it so then you'd have to look into sort of encryption i'm not going to cover that here because at the end of the day if you're going to be making a single player game does it really matter if they change the the data on their save file if that's how they get their fund them you know even games like valheim for example they use json you can go in you can change your server settings you can change your player settings and that's a multiplayer game so you know it's going to be dependent on everyone's kind of preference i don't mind players may not access their data i'm only probably ever going to make single player games so yeah that's fine and if i did want to go into multiplayer games then you could look into encryption so just before we jump straight into it i'm doing this video as a standalone you can follow this video if you've not watched any other videos however this is also going to be a prerequisite video for the save load system on my inventory series so if you've been following along with the inventory tutorial series and you want to know how to save your inventories we're gonna have to watch this one and then in the next video in that inventory series we'll adapt the code that we've written here to actually make it apply to our inventory system and if you didn't know i had an inventory system tutorial series then why not watch it i think it's quite good people seem to be enjoying it i'll put a little card up here to link you to the first video in that series and just one last thing before we get started i would highly recommend this uh official unity video after you've watched this one and it talks about serialization in unity and kind of what it all means and how it sort of works under the hood but with that said let's just get straight into this video okay so here i am in the new unity hub i'm gonna start a new project up here um if you don't have the new unity hub you can either you know update it to get it or i'm gonna be using the first person core template if you don't have this what you'll need to do is uh just create a new um urp project and then get this starter assets first person character controller um from unity it's free and you can get this kind of downloaded and installed um but if i pick the first person core it's just gonna do all of that for me so um let's call this save load tutorial and then come over to create project so while that's loading and doing its thing i just want to point out some stuff about script serialization other than what i discussed kind of in the start of the video so here's the unity um docs for serialization just to go over it a little bit more so serialization is the automatic process of transforming data structures or object stakes into a format that unity can store and reconstruct later now we can save this out in a certain format for example binary json xml but there are some different rules about serialization um there are some like uh stuff that you can't serialize so for example we've got um these are the rules to be able to serialize something so a field has to be either public or private with the serialized field attribute and it can't be static const or read-only and there are certain simple field types that can be serialized so for example custom structs with the serializable attribute which is something we may use um we've got references to objects that derive from unityengine.object the primitive data types such as ins floats doubles bool strings enums and certain unity built-in types so vector 2 vector 3 vector 4 et cetera you can um you can serialize them out and there are certain containers that can be serialized such as arrays and lists and by default dictionaries can't be serialized and if we want to serialize a custom class it has to have the serializable attribute it can't be abstract it can't be static um and it's not generic though it can inherit from a generic class so these are just some stuff to kind of you know keep in mind when we when you're working on a safe load system um but so this is all loaded now if i hit play we can just see where we are like what's going on um here's the play window so it's just a first person controller i can run and jump i'm just a little capsule um you know we can do something a little bit more interesting now such as uh keeping track of the player's position and use that to save load etc so i'm just going to get rid of the readme and let's just dock that up there okay so let's go over to our scripts let's go i'm going to go over to the assets folder and come over into our scripts these are the scripts that came with the template i'm just going to right click create um a folder and let's call this the save load system so the first thing i'm going to do is i'm going to make a class i'm going to call this class save data so let's just create a c-sharp script and we'll name this um save data okay so here's our um save data script let's just get this nice and big so the first thing we know is that for our custom classes to be uh serializable they have to be marked as such so we can outside of the class we need to put system open a square bracket and do system dot siri elizabel like that and then we can get rid of mono behavior because we don't need it to be we don't need the save data class to be attached to an object and then let's bring that and get rid of all of this and ryder is telling me so let's move this to the um save load namespace so we've got namespace save load system save loads system let's rename this folder to save load system and then open up our save data and rename that to save load system okay so this is our save data for now i'm just gonna put a public int in here and we'll just call this um index and let's set this to one so this is serializable it's it says in rider unless you know that it's serializable i'm also going to have a serialized um field and i'll do a private uh let's do a float and i'll just call this um my float so we can equal this to 5.85 0.8 f why not so this is now our save data um save data is very specific to your game so i'm putting this in just so we've got something to work with but you know your game is going to be different if are you doing a top-down 2d game with a big inventory system then you're going to need to store you know the chests what's in the chests etc if you're just doing um you know if you've got a custom map level editor you're going to need to save a completely different set of data so you know this there's no you know there's no save button there's no one click just save the state of the game you have to decide ahead of time what you want to be saved such as your player's position their inventory their current health you know all of the interactions they've had keep track of npcs and what they've been up to but with that said let's just get this set up and work in and i'll show a bit more of a practical example of how we could maybe um use this to keep track of some stuff to do with the player this is our save data and i'm going to come back over to unity and in our save load system i'm going to right-click create c-sharp script and let's call this one the save game manager and then let's open up our save game manager and again this wants to be in the safe load namespace let's get rid of this so here let's get rid of all of this as well i'm gonna zoom this back in i'm gonna make this a public static class and here i'm gonna have a public static uh save data called um current save data and we can set this equal to a new um save data the reason i'm doing this is now we can keep track of this uh save data we can access it from sort of anywhere um sorry and that should be a capital c so we've got our static save data current save data so now we need a way to be able to save this save data to file so for that i'm going to be using um json the json format so there's some stuff we can do and just to get this working so i'm going to make a public static bull method and i'm just going to call this save and we can um make this a new method and let's just return true for now so to save um later out it's actually very simple so um we're gonna need somewhere to save it which is a directory on the computer um so i'm gonna make a public const string and we can call this the directory and we can set this equal to a new string and i'm going to save in a directory just called save data this could be you know save games this could be saves save games you can name this whatever you want and i'm going to make a public string um and we can call this the file name and i'm just going to do save game dot sav you can again you can make anything up here that you want you could have um you know you could just save it as a text file you could do um dot save dot sav like it doesn't really matter you can make up your own extension um but we just need to have that as a string alternatively if you want to be able to save out stuff with different names um you could pass in a string and do file name as a string here and then when you save it out you could use this file name that you've passed in as opposed to a hard-coded one but i'm just going to use the hard-coded one for simplicity stake so let's get rid of that so the first thing i need to do is i need to make a um a temporary string called uh for directory and this is gonna be equal to the application dot persistent data path uh plus our directory which is uh this up here now application dot persistent data path is uh it's a set place by unity and it works cross-platform and it's different no matter like which platform you're in if you want to know exactly where um your stuff's gonna be saved you can do dot persistent data path unity you can see here that on the windows store apps it's going to be saved in um app data local packages uh on the windows editor and standalone player it's going to go into our local low um folder which is our company followed by the company and the product name and for linux it's you know it's it's different you know this is just one kind of call you can make and then it's cross platform so you're not having to like hard code the actual directory for everything so for me this is going to go into my local low folder um and then it's just going to tag on forward slash save data forward slash if we come up to edit um project settings and here we've got our save load tutorial that's the product name so that'd be the name of your game um company name let's just put this as uh damn puffs so looking at this my save file on windows is going to go to app data local low downpass which is here and then product name which is here so it'll be dampos save load tutorial and then it'll be in um a save data folder within this persistent path so now we've got this full like sort of path as a string and we can just say if the directory dot exists so if this directory doesn't exist okay i've noticed an issue here um i've got this string called directory and then i'm trying to get um but then directory is also a class so that's why it's getting confused so let's just rename this to save directory there we go that's fixed and we need to be using the system.io um namespace and yeah so if this directory doesn't exist this dir then we just want to um make that directory so we can do directory.create directory and then pass into so when we call save it's going to check what the the full directory including this this folder that we're going to create and if that doesn't exist then we're going to make that folder so that's the first thing that we need to do and then we're going to come down here and we're going to say string json is equal to and i'm going to use json utility dot to json i'm going to pass in our current save data and we're going to put in true here for pretty print which will make it um without this it puts all on one line and it looks gross so we want to have pretty print to true and then we can just call file dot write all text and here we're going to pass in the directory because this is the path that we're going to write all of the texts to so we need to do the du which is our full path plus our file name so this here is where if you took in a string um as a parameter of this you you'd put the string here as opposed to using the hard-coded file name and and the text that we're going to write out is our json string which is this here and then just to make this easier so i can open the folder i like to do um gui utility dot um system copy buffer is equal to our plus the um file name the reason for this is that when i call save it's just going to copy the path to my clipboard and i can just paste it into windows explorer instead of trying to navigate to the local low folder because it's a bit of a pain in the butt you know for now that's our save system done it's going to save out here and we're returning true um just to say that it was successful and you may want to put in some you know other checks and it could return false if something's going on for example if the scenes load in you can put some different checks in um but this is just so we know that the save has been completed successfully so we now need a way to call our save function so um let's go over to unity again i'm going to right click create uh empty and let's just call this um our save tester so back in unity i'm going to make um just a button that will allow us to just click the button and then we can save the game so if i come up to if i right click sorry in the project do ui and just make a new button let's just call this the save game button and then let's find this in the scene in 2d mode oh and i'm going to make the canvas um screen space overlay that's fine but i want it to scale with the screen size and then i'm just going to put in by 1080 and then let's get our button and i'm going to move the button uh just down here uh oh let's just scale it up slightly and for our text let's do um best fit just to make it a bit bigger and then i can just just put in save um while we're here i'm just going to make a load button as well load game button which we can't use just at the minute but we will be able to use it um in a second so let's go to our load text and just put load here now on our canvas i'm going to make a new script i'm going to call it just a save game tester now you might want to call this the save game manager or wait we've already called that the save game manager but you know what i mean like so this save game tester script you know this may be your game manager script you can plug this in kind of this functionality into whichever script it kind of makes sense to do so on your own game um but for me i'm just gonna here make a public void save game uh function and when this function's called it's gonna access our save game uh manager and it's just gonna save the game now if i come back over to our save game button and on click we want to find our save game tester script and just call the save game function let's just add our public void um load game function here and we can do save game manager dot uh load which we haven't actually made yet so let's just um load game here so with our load game button come down to on click drag in our canvas and then just hook in the load game function which again doesn't do anything at the minute but it will do and so if i come back over to our game and let's just set this to 1920 by 1080 so if we hit play now you know we can walk around and i can hit the save button and our game is going to save so if i hit save that work i can't tell if that button actually pressed let's just disable our um player capsule for now um i don't want it to capture the mouse if i hit save there we go the button is saved and because we put the system.copybuffer in oh actually this needs to just be the directory it doesn't need to be the file name um that is my mistake so let's just save that and reload and let's do that again so if i hit play and hit save so that's going to have copied the um to our clipboard so i can just come up here and paste that in you can see that it's gone into app data local load down pass save load tutorial in our save data folder and we've got our save game file here which is a dot sav file if you want to get to this folder without copying it to your buffer um on windows anyway you can press the windows key type in run type app data surrounded by the percentage signs click ok and then you've got to come back out to app data go into local low find your company name your project name and then the actual folder so you know it's just quicker to be able to just paste that in and hit enter now let's open up our save game and here's our save game so we've got uh index which is one and my float which is equal to 5.8 f it's added some extra stuff onto the end of it but you know that's fine um so let's just come back out of here so now we can put some different stuff in our save data as well so we can do for example a public bull just call this uh our bull sets to true do a public um vector three called our vector and then maybe set this to um a new vector 3 of 0 10 99 which again is marked as serializable so let's just close this down hit play hit save make sure we close this go back over to our project and open up our save game so now we can see that we've got uh index of one my flow our bull our vector so that's how you save out data so how would we load this so if i um stop the game let's work on loading the data now if i come over to our same game save game manager so what we need to do now is um we need to make a public static void load and let's just do save game uh load game and let's just rename this to um save game just so we're a bit more explicit about what we're loading and saving and so we've got load game and we can do string full path and this is again equal to our application dot persistent data path um plus our save directory and then plus our file name so this is the exact file that we want to be able to load um we're not loading the directory we're loading in the save data so we need to have the file name included again if you've put a custom file name in here you'll have to check that custom file name in load game and pass that in as well so we now need to make um just a some save data called um temp data or sets equal to a new save data and we can say if file dot exists at our full path so if we have a save game here then we want to um recreate our json we need to load in our json string so here just as we saved it out we want to recreate it as a json file so we can do string json is equal to file dot read all text at our full path and then we need to convert this json back into some save data so we can say uh temp data is equal to json utility dot dot from json and we want to open up some angle brackets and this is going to be the type of save data and we'll pass in our json path so we're getting our json string back into the program and then we're using the json utility to turn that string back into our class and our custom class of save data and if the file doesn't exist let's just debug dot log error save file does not exist and then at the end after all that we can do current save data is equal to our temp data so let's just check that's working so in our save game tester what i'm going to do is i'm going to do save game manager dot and then let's get the current save data and let's change the index to 10 and then let's save the game so let's just make sure that's working so our current data um index is one so let's just make sure that's closed so let's hit play and then let's hit save and let's check our saved aim save game data now so index is 10 so we've changed to that now let's check the loading is working so if i do um here in our load game what we can do is do save game manager dot load game so before i load the game let's change the index again to um 20 this time and then let's load the game and now let's debug dot log the save game manager dot current save data dot index so what should happen here is we're gonna we've changed our index to um 10 and then we've saved the game and then we're gonna change it again when we load it to 20 we're going to load the game and then print it and if our load games work in it'll print back out the 10 that we've saved and if it isn't working it'll print out 20 because uh it won't work so we've changed it to 20. so let's just check let's just check that so again we'll hit play let's come over to the console so let's save our file out and let's just check so it's definitely index 10. now let's load our game and you can see here that it's debug.log 10. so even though we set it to 20 after we've loaded the game it's actually 10. so that shows that our index um our save game system's working okay so this is all working so how can we make this a bit more game related not just a bunch of just useless junk let's get rid of all of that and just save our file so in our scripts folder in our save load system uh oh yeah and then because that's uh because i've deleted all that data we just need to get rid of this so let's make a custom struct now of some player information so if i come over to our player capsule from this starter asset and just um re-enable that okay so i'm going to add a new script to our player and let's just call this um player save data so in our player save data script um outside of the class i'm going to make a new struct called public i'll do public struct and we'll call this the player data and we need to mark our struct as system dot serializable and now we can define what we care about on our player so this is where you'd save out their you know their current inventory system uh their position so we can do a public vector three um player position we could do um public int player her current health and you know you can do lots of different stuff in here you can again it's very specific to whatever your game is that you're working on um so i'm just gonna uh i'll just leave it at player position and current health they're two good sort of test cases and it's now back in our save data class i'm going to make a reference to public um player data called um player data and set this equal to some new play data so now in our player save data um i'm gonna here make private player data and do um my data and set this equal to some new player data now in our update function we can say my data dot player position is equal to transform dot position uh we could maybe do as well a public quaternion player rotation and then we can do my data dot player rotation is equal to transform dot rotation so every frame i'm going to be keeping track of our um player position so this is going to be like a constantly saving thing you know you might not want to do this on every frame you may want to set up your own kind of custom event so every um you know every 30 seconds it logs the play position if you've got an auto save system that's a good way of doing that or like a checkpoint system and when a player passes through a checkpoint then you'll only then you'll need to update this but i'm just going to do it every frame and i'm going to say that if um oh and then let's do current health as well my data dot current health is equal to current health which is going to be a public void i don't sorry public int and current health let's just default this to 10. say if keyboard dot current dot um r key dot was pressed this frame so this is using unity's new input system uh which is enabled by default with the first person template if you're doing this in your own game and you're not using the new input system um you know you'll just do like input.getkeydown keycode r but this is using the new input system so that's how you do that so if the keyboard.current.r key was pressed this frame then let's call our savegame manager uh let's actually make a new um we're gonna have to do a couple of things so we'll do save game manager dot uh current oh dot current save data dot player data is equal to my data and then we'll do save game manager dot save game so every frame is going to be updating the player position player rotation and the current health and then when i press r we're going to set our save games current saved game we're going to set our save games current save data player data which is this struct we're going to set that to my data which is this version that we're keeping up to date every frame something else that you could do is you know instead of doing my data you could do the save game dot current save game dot play data and update that directly um i think this is a good way of just keeping track of this keeping it all in one script and then updating the save game manager when just when needed um so we can set our player data equal to my data and then i'm gonna do if keyboard dot current dot um i'll just do the t key three uh the reason i'm using random keys is because i don't know what's um you know like wised space and all that setup already and i think r t should be fine to use so if current dot t key dot was pressed to this frame then we're going to do save game manager dot load game and then we can say my data is equal to the um save game manager dot current save data dot player data and then from that i can say transform.position is equal to mydata.position i can do transform.rotation is equal to mydata.playerrotation oh player rotation and then i can say current health is equal to my data dot current health and rider is telling me to cache my transform so i'll do that um so let's go over to unity now um and i don't actually need these save game buttons because i can't use them uh when the capsule is activated so if i hit play so we're walking along we're updating our position and let's select our player capsule so you can see our position up here let's go over here so -15 on the x uh below one on the y minus one on the edge and we've got a y rotation of 84.95 and our current health here is 10. so if i hit the r key that's gonna have saved our game and it will have copied the directory to the path i'm already there open up save game and we've got player data so player position minus 15 just minus 15 y um zero point eight minus one that's all good current health equal to 10. so if i now change our current health to say five and then i run over here and i hit the t key gonna load our game and reset our player's position rotation and health so do uh t you can see it's loaded us back and it's put us with -12 is that where i was [Music] okay that was a bit of a pain in the ass um okay so there was a bit of an issue uh with our function the problem was that with the first person controller template uh this first person control the script so somewhere in this script we're setting the position uh with the character controller i think it's here this controller.move obviously that's that's the bit that's moving the player and this is called every frame now what was happening is that we'd hit the load button and then it would set our position to my player.position this was all loading fine but then immediately on the next frame there was a bit of like a hangover kind of somewhere in the script um and it'd move you back to the frame that you just were and you'd have to press load a few times for you to be the one that got called last and actually for the save data to overwrite the actual data so one solution for this is i've got a reference to our first person shooter controller which is our first person controller here and i'm going to get that component in the awake function and then what i'm going to do is go keyboard.current.t key was pressed this frame i'm going to disable the controller i'm going to do all of our jazz and set the transform etc and then after 0.25 seconds i'm going to re-enable the controller which will basically stop this overwriting what we're doing here so while the game is saving none of this is going to be happening and then once it's finished saving we just got a little bit of a buffer to re-enable this another way of doing this would be in our save game manager uh have some you know public static uh unity actions like on save game sorry on load game start and then we could have onload game [Music] finish we could call on games unload game starting here after it's finished loading we can do onloadgame.finish and invoke that and then in all of the scripts that need to know when a game's loading i'm gonna get rid of this this was just for me trying to fix it that because that didn't seem to work for some reason so in all of our scripts here we could subscribe to our on enable we could subscribe to our save game manager um and detect once the game started loading you know do some logic based on that and then once the game is finished loading re-enable anything that we need to like actually enable um but for our case here um this works just fine so let's go back over to unity and let's see it not work now and be a pain in my book so i'm going to come over here i'm going to save the game by pressing r we can look at the save data file so we've got current health 10 our rotation and position that's all the same the rotations change because obviously i looked down um but that's all groovy now if i come back over to the game i'm going to change our health to three i'm gonna run over here let's come in here and then i'm gonna press t on the keyboard and that teleports us back and we can carry on moving and we've got our all of our health back let's you know go up so we've got 20 health now i'm going to come over here i'm going to save it looking up this staircase so press r to save the game i'm going to run back out here press t and there we go we're looking up the staircase and we've got our health back uh our health stayed at 20. if i set this to 10 and then press t and load it there you go looking up the stairs got our 20 health back so from this point on it's kind of up to you what you want to do and what you what you class is needing to save um you know this is just a good kind of example of that we've got our player save data and like i said you could have your inventory system um you know the amount of gold that the players got instead of the health the unlocks that they've got you know you could have a public bull um double jump acquired uh this could be you know and then you can get your double jump and then you'd keep track of that here um you can do whatever you whatever you need to do keep track of completely up to you but yeah if you've got any questions do let me know in the comment section below if you want the project file for this you can play around with it and have a look at how i've set stuff up that will be available over on patreon.com forward slash dan pass if you've got any issues with the project or any questions that you want clarified let me know in the comments below or head over to the discord server which is linked in the description and i should be able to help you there but in the meantime thanks for watching and i'll catch you in the next one bye
Info
Channel: Dan Pos
Views: 12,025
Rating: undefined out of 5
Keywords: unity save load system, unity save game, unity load game, how to save and load game in unity, unity tutorial, game dev, indie dev, indie game
Id: f1cqInqB9qk
Channel Id: undefined
Length: 43min 53sec (2633 seconds)
Published: Sat Feb 12 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.