Saving and loading games with Godot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello golders when you make a game you will eventually come to the point where you want to add a system for saving and loading games implementing saving and loading can be surprisingly difficult though so we are going to explore how we can save all relevant information from our game and load it back reliably we also going to have a look at some techniques to make sure that our safed games still work even if we make changes to our game this is an intermediate level tutorial to make the most out of it you should be familiar with the basics of the gdau engine like noes signals and the GD script language so this is our game a sides scrolling underwater game the player controls a Little Submarine and needs to make it to the exit of the the level right now that's all the game does but we will add more stuff later we're not going to get into a lot of detail on how this was made because this is a video about saving and loading but if you want to have a deeper look inside you can get this whole project from GitHub there's a link down in the description for now we want to save and load the position of the player and before we do this let's first have a look at what saving and loading actually means when we want to save a game we need to collect all the important information from the game and then write this information to some file on the file system and when we want to load the game again we first need to load the saved information from the file system and then we need to use this information to restore every part of the game to be in the same state as as it was when we saved it now that sounds rather simple so let's see if it really is let's first build a little bit of infrastructure I have already added a save and a load button here and to keep things nicely organized we put all saving and loading into this safer loader note here the buttons are already set up to call the save game and load game function of the save load the note let's Implement saving first so we have something to load later we want to save things to a file so the first thing we need to do is to create a new file we can do this with the file access class so let's maybe go with war file equals file access. open and we save this to rest save game. dat and we want to open the file for writing so we use file access. write you probably have seen this rest prefix a few times already when working with gdau this is a buil-in special prefix for the current projects folder so this line will open a new file in our project folder named save game. dat now we want to store the position of the player and we don't have access to the player in the save loader note right now so let's make the player a seen unique note right click access as unique name and then we can control drag the player node into our script and now we have the player a available the file access has a function called store War which we can use to store variables into the file so let's store the player's Global position we call file do store war and then we can use player Global position so this will write the global position of the player into the file and now all we need to do is to close the file and we should have a save game let's try this out and start the game so we move the player a bit and then we press the save button and this should do the trick so let's check out our project folder and here we have our save game. DAT file now let's have a look inside and see what we actually stored so we are open this in an editor so this seems to be some binary file which we cannot really read so we will have to load it again to see if it actually worked so let's go back to our save loader and to load the file again we first have to open it well we already know how to to open a file so we just can copy this line from up here but we will change this right to a read because we want to read the file and not write to it now we can use the getar function to load our player's position back so we write player. Global position is file. get war and now again we just need to close the file and then we can see how it works so let's move the player around a bit and then press load and it looks like loading the game actually worked our player position is restored this is fantastic so can we call it a day then are we done well for a very simple game for example if you just want to save a high score or something and this kind of saving and loading could actually work just fine but if your game is a little bit more complex than that then this way of saving a loading is very likely not going to cut it let's see why so currently we only store a single piece of information the player's position our player also has health so maybe let's store the health as well because we cannot really see if the health saving Works without actually damaging the player we will need to add an opponent our opponent is going to be the fish let's start a game and see how the fish works so the fish is going to follow our submarine and tries to Ram it and if the fish hits the submarine then the fish destroys itself and the player loses some health okay so let's Implement saving and loading for the health of our player when we look at the player script we can see that it has a variable called health so we need to save and load this variable we can modify our code in the save loader to do this so we write file store War player Health we also add it to the load game function player health is file getar okay let's play again but before we save a new game let's try if we can still load our old saved game well it seems our old saved game can no longer be loaded because we try to load the health here and our old saved game doesn't have the health well that's not great but we're still in development so it's not really a big problem right now let's try saving a new game and loading it again so we restart the game and now let's move around a bit and avoid the fish and then save now the fish can hit us and we lost some health so let's try to load again that didn't work either so what's the problem now well when we use the store War the variables are stored in the file in the same order in which we call store War so we first store the position and then we store the health now when we call getar gdau starts at the beginning of the file and when we now read a variable from the file well we get the first variable that we stored which in our case was position but we tried to assign this to our health and that is why we get this error so we need to make sure that when we load the variables from the file we do this in the same order as the order in which we stored them and that's why we need to move the loading of the health down one line let's give it a spin so we let the fish damage our submarine and then we load again okay that seems to work fine but this approach is very brittle it's very easy to roduce a buck by just inserting a line at the wrong place also we break our saved games every time we add something new that we want to save we also have no really good way of saving the fish because the fish may or may not be in the level anymore when we press the save button and finally we save to our project folder this will not work after we have exported our game because then the project folder is read only so we need a better approach that fixes all of these problems so we just saw that writing variables into a binary file with stor war is really brittle so what other format could be used for this something that is recommended often is using Json so what is Json well it is a text based file format which allows us to store structured information it is quite similar to a gdau dictionary it has key and value Pairs and it also allows for storing of arrays gdau even has built-in support for converting a dictionary into Json and back so let's maybe give this a spin and see how far we can get with it let's start by making an empty dictionary and maybe name it saved data now we add the player data as entries into into our dictionary so we create an entry for the player health and we create a second entry for the Player's Global position this fixes our ordering problem nicely because the dictionary doesn't care in which order you insert the contents so this looks like a good start let's also change the file name from data tojson and we also move it over to the user folder the user folder is a folder that is guaranteed to be writable when we have exported our game so it really makes sense to put our saved games there instead of the rest folder okay now we need to convert our dictionary into Json and write it out so we write War Json is json. stringify saved data this will convert our dictionary into a string that contains Json now all we need to do is to write out our Json to a file and we can do this with the file access store string method that we're saving and now let's also update our loading code first let's fix up the file name here and we want to get the Json string back from the file and we can use the get as text method for this so we write file get as text now we have a Json string and we need to convert it back into a dictionary we can do this with the parse string method on the Json claw so we write War save data is Json parse string Json now we have our dictionary back so we can extract our save data and again because it is a dictionary the order in which we extract our data doesn't matter anymore so we can extract the Health before the position and it should still work so let's extract it we write player Health equals save data player health and player Global position equals safed data player Global position okay great so let's give this a spin and see if it actually works we again try to escape the fish for it and then save and then the fish hits the submarine let's press load and we have a problem the error message says that we try to read a key from the dictionary that doesn't exist and you probably already spotted the arrow we have a typo here in play our health so let's fix this and try again so we let the fish hit our submarine again and then load the saved game okay looks like we still have a problem it seems that for the player position we get no Vector two back but rather a string that's not good so let's maybe have a look at our save file and see what was actually written into it now where is that saved file we can consult the documentation for this and we see that on Windows we need to go to this path so let's copy this and then paste this into an Explorer and then we need to append the name of our project here and here we have a Json file now let's have a look into it the Json file contains text which which is great because we can actually see what's going on and it looks like that when we converted our dictionary to Json the vector 2 with the position was converted into a string with the coordinates and this is not parsed back into a vector 2 why is that well the Json format has no notion of vector 2s or any other built-in GDOT types all we have is a name which is player position and a string with some information in it the Json parser has zero information about what this player position thing actually is so that's actually a pretty big downside let's see if we can fix this by Saving out our Vector two components separately so let's go back into gdau and then let's split up the saving of the position into two parts so we duplicate this line with the player position and in the first line we save the x value and in the second line we save the Y value we also need to fix up our loading so it loads our new split position back so we duplicate this line again and we restore the X position in the first line and the Y position here in the second line okay let's try this again we start the game and escape the fish for a bit and press save now the fish hits us and we press load again and it actually works fantastic so what switching the file format to Json and Improvement then well let's see on the upside we have a readable file format so now we can look into the save file and see what's going on this is an improvement also we could save the data in any order so this was not as easy to break as our first solution where the order was important so that's also an improvement but we also added a few more problems with this format first we need to move everything into a dictionary which makes it really easy to introduce a typo as we have seen and the second more important problem is that Json is a generic data interchange format and has no notion of those built in types like vector 2os so we need to manually convert each of these special types into a Json structure which is probably okay if we have one or two of them but it can quickly get out of hand if we have more it's also an easy thing that we can forget so this doesn't really help with maintainability so I think that while Json has somewhat improved the situation here it is still not where I would like it to be one of the big downsides of Json was that it had no notion of gd's build-in data types so what would happen if we don't actually convert our dictionary to Json but rather write it to the file with store War well that's a quick change to make so let's see if this is improving things first we need to change our file name back to data because it's no longer going to be a Json file now we can remove the split of our Vector 2 here so we are back to what we had before where we just saved the player position into our dictionary now we can remove the Json stringify and replace the store string with a store bar where we put in our dictionary now that's it for this saving site so now we can go to the Loading site here we also need to change the file name back to data we can also remove the Json parsing here and we can just call getar to get our dictionary back and like on the saving side we can remove the split of our position so we just assign the value from our dictionary back to Global position so let's try this out we run away from the fish and now we save and we let the fish hit the submarine and then reload and that worked nicely okay so let's have a look into our saved file again as expected we got back some binary notation we can actually see the names of our dictionary entries here but the rest is really not readable to us so is this an improvement now well it's really only a different tradeoff we got the native support for G's data types back so this is an improvement but on the other hand we lost our nicely readable save format so now it's harder for us to debug problems with our saved games we also still have the problem with the typos in the dictionary keys so in the end we really just traded one problem for another we can fix the remaining Problems by using custom resources a custom resource is a class that extends gd's builtin resource class so let's write a custom resource for our safe game we create a new script and then we name it saved game. GD and we're going to remove all of this first we need to give our new class a name so we write class name saved game and we're going to extend resource so gdau knows that this is a custom resource resources can export variables similar to how notes can export variables and only exported variables will be saved and loaded by gdau so let's export two variables one for the player position so we write export bar player position and that's a vector two and a second one for the player Health which is going to be a float great now we have a custom resource and we can use this to save our game so let's get back to our save loader instead of creating a new new dictionary we now create an instance of our custom resource so we type or saved game and this is of type saved game is save game new because we added this typ hint we now get aut complete when writing data into our save game so we can type save game player health is player health and save game do player position is player Global position this is great because now we canot have typos anymore so now we want to save our custom resource Gau has built-in support for doing this so we actually don't need to manually open write and close the file anymore so we can delete all of this and we can just write resource saver do save then the resource that we want to save in our case it's the saved game and then the path where we want to save the resource too so user slav game. Tes the resource needs to have this Tres extension which stands for text based resource because only then Gau knows how to load it and how to save it that's for saving so now let's try to load this resource again Kido has built in support for loading resources as well so we don't need to open and read the file anymore so we can delete this and then just write War saved game and this is of type save game is load user save game Tres for some odd reason we don't get auto complete here even though we added a type to the variable this is some limitation of the editor that is hopefully fixed in a future version of gal for now we can fix this ourselves by adding an as saved game here to our load and now we get proper order complete and we can write our loaded data back to the player so we can write player. Global position is saved game player position and then we can repeat the same for the health this looks pretty neat now let's see if this actually works so we try to escape from the fish and we save the game then we let the fish hit us and we load the game again and this works okay so let's have a look at the save file now we go to our folder and open the TRS file and this looks like a nicely readable file so we can see what's going on so is this an improvement now well we still got all the good things of our previous solution so that's great we also eliminated the aror prone strings because we now have nice order complete and we have a nicely readable save file which will help us when we want to debug what we have saved we can actually direct this file into our project and double click it and we see its contents nicely in the inspector so saving games into custom resources seems to be a very nice approach because gdau does all the hard work for us and we can focus on the actual loading and saving logic there are some issues coming with resources that we need to take care of but we will address them later when we get there for now let's try to save and load our fish as well so we can full fully save and restore our little game here I've prepared a second level here which has two fish built in and now we want to save these fish as well the fish have no health so we only need their positions but how does our sa loader get to know about the fish for the player this is really easy there can only be one player so we can use a unique name to access it but in a level I can place as many fish as I like so using hardcoded note paths will not work here we can solve this problem by putting the fish into a group so let's open up the fish scene and then select our fish and then here on the right hand side we go to the Note Tab and then to groups if you haven't used groups yet a group is basically a little sticker that you can put on a note we can use this to quickly find all nodes that have the sticker our fish already is in a group called enemy and this is so it will not attack anything else that has this enemy sticker so fish do not attack other fish let's give this fish a group called fish so we can quickly find all fish in our Sab Lo note so we type in fish up here and then we click the add button now we can go back to our saveal loader note and collect the positions of all fish so we can save them so let's start a loop over all fish so we write four fish in and to get all fish we need to get the scene tree with get tree and then we can call a function called get nodes in group and we give it the name of the group and this gives us all the fish that are currently in the game but we need some space in our saved game resource where we can store these positions so let's go back to the sav game script and then add an array for saving the fish positions so we add export War fish positions and this is an array of Vector 2 and we initialized this with an empty array so we don't have to do any null checks later now we can go back to the save aler note and we can write the fish positions into our save game so here we have our loop again and here we can just append the global position of the fish to the fish positions array like this great we have now saved the positions of all fish so let let's think about how we can restore them in our safed game we have the amount of fish that we had when the game was saved and their positions but when we press load there may still be fish in the currently running game so the first thing we need to do is to Clear the Stage this means that all fish that are currently in the game need to go now we can restore the fish from the saf game so for each entry in the saved game we create a new fish we add it to the world and then we move it to the position that we saved for this fish so how does this look in code then well the first part is to Clear the Stage so we need to get all fish that are currently in the game we already did this when saving so we can just copy this part now we can remove the fish from the tree we do this by removing them from their parent note so we write fish get parent remove child fish and then we call Q free to delete them technically it would be enough to just call Q3 on each fish but then the fish will remain in the tree until the end of the current frame and this can have unwanted side effects so as a rule of thumb it's usually a good idea to remove nodes from the tree before calling Q3 on them now our stage is clear and we can start restoring the fish from our saved game so we make a new Loop to walk over all fish positions in our safed game so four position in safed game Fish position positions we want to create a new fish and for this we need the fish scene so let's control drag it in and then we create a variable for it so we call this fish scene now we can instantiate the scene to get a new fish so more new fish is fish scene instantiate and now we need to add our new fish to the world we don't have a reference to the world route right now so let's drag one in and now we can go back to the restore and add the fish to the world route so World rout at Child new fish the last thing we need to do is to set the position of the new fish so it has the same position as as it has in the saved game new fish Global position is position so let's try that out so we first save our game and then we pull one fish and let it hit our submarine and maybe pull the other fish as well and while we're pulling it we press load and it looks like everything is properly restored that's great now we can do a save and restore that works no matter how many fish we have in the game so let's say we want to add a feature now so that the player can fire Torpedoes at the fish I have already implemented this so let's have a quick look at it the player can now move around and fire Torpedoes and if a torpedo hits a fish then the fish is destroyed let's see if saving and loading still works after I have added this feature so we fire at this fish then we save the game and now we fire at the other fish as well and then we load the game that which is saved hm the first fish magically reappeared so what is going on here let's try this again fire at the fish and now let's pause the game so we can have a look into the remote tree here we have our fish and when we look into the inspector we can see that it is marked as dying so it only lives on to play a few visual effects and will be gone shortly but because we save all fish no matter in which state they currently are our fish is magically resurrected once we load the game again so how can we fix this our sa loader could check whether the fish is about to go away and only record its position if this is not the case but if we do it this way then our sa loader will need to know even more about the internals of the fish and if we add our Torpedoes later our sa loader would now also need to know everything about Torpedoes so this isn't really scalable because we constantly need to update our sa loader whenever we add or change items a better way to fix this is to split the saving into two parts a collection phase and a saving phase so first we have a collection phase the sa loader sends an event that we are about to save the game and with this event the sa loader provides a list now every node that wants to save some information can put it into the list so each note can decide if it needs saving and what needs to be saved in this way the sa loader doesn't need to know anything about the internals of a node and this should also fix the problem that we just saw where we saved a fish that would soon disappear the fish would now see that it's going to be gone soon and wouldn't save anything at all now the sa loader has the list with all important information and we can go into the second phase and put this list into the saved game so that's saving how about loading before we load a game we need to remove any existing fish or Torpedoes or whatever else from the game so we split the loading into two phases as well first our sa loader is going to send another event to notify all the nodes that we are about to load again now every node that is dynamic like the fish or the Torpedoes will destroy itself but notes that are static like the it will do nothing now our level is clean and the sa loader looks into the list of save data and for each item in the save data the sa loader will create a new note and add it to the world rout and then the sa loader will give the save data from the list to the new Noe and the new node will restore its internal state from the save data and this this way the sa loader doesn't need to know anything about the internals of this knowde okay so let's implement this we start with saving so we need to notify all interested noes that we are going to save the game now and we also want to give the interested nodes a list where they can add their save data too so we Define a contract every note that wants to save data must have a function named on save game and this function takes an array to which the node can add its save data let's add this function to our fish so we open the fish script and then we write a new function we call this on save game and this takes an array of save data as a parameter now the compiler is unhappy because it doesn't know what say data is so we need to create this Clause so let's create a new script next to our saved game Clause here and we name this saved data. GD and we are going to delete all of this first we need to give this class a name so we write class name saved data and like our saved game this class extends resource now what data would we like to save in general something that we're always going to need is a position so let's add that right an export War position Vector 2 also when we want to restore a node we need to create a new instance of it so if we want to restore a fish we need to create a new fish and if we want to restore a torpedo well we need to create a new torpedo and so on in order to create a new node of the correct type we could just instantiate a scene but we need to know which scene to instantiate so let's add a new export or scene path and this is a string so when we load the game we load the scene from this path then instantiate it to get a new fish or a new torpedo or a new whatever now we can go back to our fish and use this the first thing we do is to check if the fish is currently in the process of leaving the game for this the fish has a dying flag and if we scroll down here to the die function we can see that this flag is immediately set when the fish dies and then the fish just plays some animation and finally leaves the game so when this dying flag is true then we don't want to save the fish anymore so let's go back to our onsave game function and let's add this at the Top If dying then we do nothing we return now we create a new save data object where we can put the information that we want to save so or my data is save data new and we write the position and we also write the scene path we can drag in the path without pressing control so we just get the path to the scene like this but now that I think of it hard cting Puffs is something that we would like to avoid because it makes it harder to rename or reorganize things instead we can use the scene file Path property on the current node to find out in which scene this node is now we need to add this save data object to the array so we can just append it and saving is done for the fish now now how do we call this function from our sa loader the sa loader could walk over all notes in the tree but that would be pretty slow if you have a lot of notes and most notes probably won't be saved anyways instead we make a new group which we call game events then our sa loader can call all the notes in that group which is a lot faster than walking over the whole tree so let's add the fish to the game events group so get back to the fish we go to the Note Tab and then to groups and we add a new game events group while we're here we can also remove the fish group because we don't need it anymore now we can go to our sa loader and update the saving code first we delete the code that was specific to the fish and now we need to call the on safe game function on all nodes in the game events group lucky for us G has already something built in for that we can write get tree call group and then the group we want to call which is game events and the function that we want to call on this note which is on save game now we still need that list where we collect the save data so let's make this we create a new variable called save data this is an array of save data and we initialize this with an empty list and then we can add this as a parameter to the call now let's again go over what this does call group will walk over all the nodes in the game events group check if the note has an onsave game function and if it has it will call this function the the node will then decide if it wants to save something and if so it will add data to the save data array that we give in so when the call group is finished our array should now be filled with data that we can save so all that is left is that we add this to our saved game we don't have a space for this in the saved game resource yet so let's add one we can remove the fish positions because we don't need these anymore and instead we add an array of save data so export War save data and this is an array of save data now we can go back to our save loader and push the save data into our new field okay so let's try this out we start the game and we press the save button and now let's have a look at our saved game file and here we can see that the save data of our two fish has arrived in the save game file and we can also see the array of save data in our saved game resource so it looks like saving has worked pretty well now we need to load this back again so let's go back to our sa load in the loading part we still have the old code that deletes all fish we are going to generalize this with a call to our game events group as well so we make a new contract if you need to do something before the game is loaded then you need to have an on before load game function so we can replace this Loop over all fish with just another call to call group get three call group game events on before load game our fish currently doesn't have an on before load game function so let's add one we go to the fish script and we add a new function on before load game and there we remove the fish from its parent and free it back to the sa loader now our stage is clean and we can load in the save data so let's first remove this whole fish code here because we're not going to use that anymore and we can now walk over the save data and restore our notes well let's start with a loop say four item in saved game save data and next we need to load the scene from which we instantiate our restored no we have saved this scene in our scene path variable so we can just write our scene is load item scene path as pack scene now we can make an instance of the scene I say while restored Noe is scene instantiate so now we have our restored Noe back and we can add it back to the tree and we can just add it to the world rout here so World root at child restored note finally we need to give our restored node a chance to restore its internal settings for this we're going to need another contract which goes like this if you want to restore yourself from save data you need an onload game function so we check if the restored node has an onload game function if restored node has method onload game and if so we call it with the save data so restored Noe onload game and we give in the item so now all that is left is that we need to add an onload game function to our fish so let's go back to our fish and create a new function called onload game and this takes a saved data object and inside of this function all we need to do is to restore the global position of the fish okay so let's try out if this actually works we start the game now we fire at one fish fish is gone we save the game fire at the other fish fish is gone and now we load the game and everything is properly restored great we have our generalized saving system now so let's add saving and loading for the Torpedoes we open the torpedo SE and now we need to implement our contracts first we need the node to be in the game events group so let's do this we go to the Note Tab groups and add game events now we can go to the script to add the contract functions that we need because they will be very similar to The Fish let's copy them over from there to save some time so we go to the fish we copy all the methods here and and we paste them in the torpedo has the dying flag as well so this code should immediately work we also want to remove all currently flying Torpedoes when the game is loaded so we keep the on before load game method as it is and on restoring we also just need to restore the position so this should work fine as well so let's give this a try we start the game and let's fire a few Torpedoes and then save now let's load this again huh our Torpedoes are loaded and the Precision also looks okay but they are flying into the wrong direction so it looks like just saving the position alone doesn't work here and we also need to save the direction into which the torpedo is flying otherwise it will be just the default Direction so so let's go back to our torpedo code and here we have the property which is holding the direction so we need to save and restore this property as well now we have a problem though because we don't have a property in our save data to hold the direction also the direction is only needed by the torpedo so I'd rather not have it in the saved data class we can solve this by making a custom safe data class for the torpedo so let's add a new script in the torpedo folder and we name it saved torpedo data. GD now we delete all of this and first we need to give this a name so we write class name saved torpedo data and because we can only store saved data objects in our saved game our saved torpedo data needs needs to be a subclause of save data so extends save data and now we can add a property for our Direction export or Direction Vector 2 okay now we have a custom data object that we can use to store all the data that we need for the torpedo so let's go back to the torpedo script now instead of creating a Sav data object here we now create a saved torpedo data object now we can also save the direction so we write my data direction is direction we also need to restore this direction so let's fix up the onload game function as well and when we look at the signature we technically get a save data object back but we know that what we will get back is the exact EXA same object that we have written to the array up here in save game so we know that this object this save data is actually a save torpedo data so let's add a variable where we cause this data to saved torpedo data so we get a nice autocomplete later and now we can patch the global position line we use this my data variable now and we can add a new line that restores the direction so direction is my data. direction okay so let's try out if that works restart the game and now let's fire some Torpedoes save the game and load the game that looks funny so our Torpedoes move into the correct direction but the visuals are not updated to reflect the direction so we need to make sure that we update the visuals of after we have restored our internal variables so let's go back to the torpedo code and in this case it's rather easy to solve we just need to rotate the torpedo so it looks into the direction it is moving we can use the look at function for this so we can just add look at Global position plus Direction now let's try this again start the game reload the game and our Torpedoes look correctly so we were able to save the torpedo without having to modify our saal loader just by following the same few contracts that we made earlier and used in the fish and this way we can very easily add new game elements and have them properly saved and loaded we now have a system that allows us to set save and load all kinds of game elements but what happens when we make changes to our game how does this affect our safe games let's say we want to add a new game mechanic to our fish that when you hit the fish with a torpedo it doesn't immediately die but rather becomes smaller so it is harder to hit and only if you hit it a second time it will disappear so let's quickly implement this we go to the fish and there we have a take damage function so we just do a really hacky implementation of this so we write if scale X is less than one then the fish dies and otherwise we just scale the fish down so we set it scale to a vector 2 of 0.5 to 0.5 let's quickly try this out we fire a torpedo at the fish and it gets smaller when it is hit the first time and if we hit it a second time it disappears we haven't modified our safe code yet so the size change will not properly restore let's fire at this second fish and now we save and when we load this game again the fish again has its original size so let's go back to the fish and fix our save and load code so that the size change is properly saved and restored like with the torpedo we now need an additional field in our save data to store the fish scale so we make a new Clause called saved fish data right click new script we name it saved fish data and we remove all the code inside now we can give this a name saved fish data and like our saved torpedo data it extends save data and we add an export for the scale of the fish which is a vector two let's go back to the fish's on save game function we change the save data to saved fish data and we add a line that saves our scale so my data. scale is scale that's it for the saving part and now we need to update the loading part down here in the onload game function we now get a saved fish data instead of just the save data so let's add a cost for this very much like we did for the torpedo so or my data is save data as saved fish data and then we can use use our my data variable to restore the position and we also add a line to restore the scale now our saving and loading implementation should be able to properly save and restore the size change so let's give this a try but before we save the game let's first try to load a game that we previously saved this is something that the player would certainly do after a game has been updated so let's click load and if you guess this wouldn't work you guess correctly we get an error here because my data is null why is it null well we cast the save data into a Sav fish data but if we look in the debaca panel here we can see that save data is still a Sav data object and not a saved fish data object and this is because we created the saved game with an older version which didn't have the saved fish data yet so our loading code needs to be able to handle both versions the old version and the new version in our case that is quite easy to do first we restore the position because that is the same for both the Old and the new version of the safe game so let's move this to the front and now we can check if we actually got a saved fish data and only then we restore the scale so we do a check if save data is save fish data and only then we use this cost variable and restore the scale this way when we have an old saved game we just keep the default scale and when we have a new saved game we properly load the saved scale well let's try if this works now we start the game and then we fire at a fish and load our old safe game now properly loads this is great now let's fire at a fish and save the game so we get a saved game in the new format with our saved fish data okay so now we restart the game and then we load this new saved game and this seems to work nicely as well so we see that when we add new mechanics to the game we need to be very careful that our changes don't break any previously saved games there are a lot of ways to break saved games and showing them all in this video would take way too much time so I have created an article which shows a lot of ways to break your saved games and it also shows some strategies on how to handle changes in saved games so you can still load older saved games after an update if you would like to read this article you can download it for free there's a link down in the description at the beginning of this video we had a look at different file formats in which we could save our gay we finally settled for G Do's resource format and then I said that there are a few issues with this format that we need to address so far everything has worked out nicely so where's the problem the first problem is that resource files are an easily editable text based format so I could easily open my saved game in an editor and give my player a million Health points well let's open our save game and now we can go down here where the player is saved and it says player health is 200 and I Chang this to 1 million now let's start the game and when I bump into a fish I lose quite a chunk of Health but if I load my saved game now I can bump into a fish all day long because I have so much health that it doesn't even matter so when we have an easily editable save format it's very easy for players to cheat on the other hand having an easily editable safe format makes it really easy to debug problems with our safe games so what we do about this well this pretty much depends on your game and your requirements for a game like this I would have no problem with people cheating their way through it after all they're just ruining their own experience but in a more competitive environment this may be a problem for example if we give out an achievement for finishing the game without ever dying such a cheat would make this achievement meaningless one way to address this problem is to verify the data from the saved game before we apply it for example we could check the player Health to be within a suitable range let's quickly add this to our sa load we can use the Min function to only allow values less or equal than 200 for the player health so we write player health is min then the player heal from the sav game and then 200 and now the player Health can never be larger than 200 so setting the player's Health to 1 million doesn't work anymore because it will be limited to a save value after loading we could also switch to some binary format or even encrypt or digitally sign our saved games to make it harder to do this kind of thing but this will not really stop a determined person it's not really that hard to write a little program in gdau that can read or modify binary data or extract the encryption keys from our game so verifying the data in our saved game before we load it seems to be a better solution here in addition we can still keep our nice text based format the second problem with the resource file format is that it allows to inject scripts into saved files and these scripts are immediately executed when the sav file is loaded I've prepared a a copy of our latest saved game here where I have added some injected script now let's see what happens when we try to load this saved game we start the game and then we press the load button and sure enough our injected code is executed okay so that was a nice demonstration but why is this a problem well imagine a malicious person who adds code to a saved game game and then publishes the saved game on the internet then players would download the saved game and when they load it some malicious code would run on their machines and this is obviously not good it is also problematic if you have some competitive multiplayer game because a player could then use this code injection to load all kinds of cheats into the game gdau does not provide a buil-in way to prevent the execution of unwanted scripts and resources so do we need to switch to Json or some other format after all fortunately we don't what we can do is to check our safe resource file for malicious patterns before we load it to make this easy for everyone I have created a small add-on which does this let's quickly add this to our project we go to the asset library and we search for Save resource loader and now we click on it press download and in this install window we deselect the example folder because we don't need it for our project now we can install it and finally we need to go to project project settings plugins and we need to activate the plugin here after installing new plugins it's always a good idea to reload the project and we can do this in the menu project reload current project now the project is reloaded and we can use the plugin so let's jump into our save loader to the place where we load the saved game currently we load this by simply calling this load function the plug-in provides a new class called safe resource loader which is used to load the resources safely so we can change this to save resource loader do load and then the path to our safe game the save resource loader will now check the saved game to verify that it does not contain any embedded code or loads any resources from an unsaved location if the saved game is considered as unsave then the save resource loader will return null and refuse to load it so we need to add a check to our code to verify this so we add if save game is null and we just print something like saved game was unsa and a board in the real game we would probably make some better error handling but for now this is enough so let's try it out we press load and the game is not loaded because it contains potentially malicious code this is great but should we really do this wouldn't it be better to use a format like Json where no code can be executed well resources make it really easy to store and load large graphs of nested objects and they also support all of gdau building data types as we have seen when we explored the various formats that were available if we want to do the same thing with Json or any other format we will need to manually serialize and deserialize all of our objects and also manually convert all of gau's data types like vector 2os this is a lot of work just to replicate something that the resources already provided out of the box so in the end you will need to decide for yourself which format you want to pick just look at your game and look at your requirements and then choose a format that will work best for you you can still use all the techniques that we explored in this video Even if you use a different file format for your saved games in this video we learned how we can save and load game games with gdau we started with a simpler approach using stor war and extended this approach to become a bit more robust then we had a look at various file formats that we can use to store saved games and we learned the pros and cons of each of them we learned how we can use a combination of groups and gdos call group function to save and load game elements in a generalized way so we can easily add new game elements to our saved games we learned how changing game mechanics can break existing saved games and that there are a lot of ways for breaking saved games if you want to know more about these be sure to download the article that is linked in the description finally we had a look at some implications of using resources for saving games we learned how players could cheat by modifying saved games and how we can address prce we also learned that resources allow for script injection and looked at an add-on that can help with this saving and loading requires a lot of planning and thinking especially when your game is more complex I hope that this video and the extra material will help you to implement saving and loading for your game if you have questions or want to share your own ideas or approach on this topic please post them in the comments if if you like this video please give it a thumbs up and consider subscribing to the channel to get notified when new videos are posted thank you very much and happy Goering
Info
Channel: Godotneers
Views: 44,224
Rating: undefined out of 5
Keywords: JSON, code injection, compatibility, gamedev, godot, loading, resources, restore, save, saving, security, updates
Id: 43BZsLZheA4
Channel Id: undefined
Length: 69min 37sec (4177 seconds)
Published: Sat Dec 09 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.