 Hello, everyone, it is CryptoGround here. Welcome back to another Unity Out of Game tutorial video, this is episode 4.0, and today we're going to be implementing our save and load system. So this video will be divided in four parts. The first one will be the save system. The second will be the load system. Third, we will check to see if our file exists. And the fourth one will be actually implementing the save system. If you guys end up enjoying this video, one thing I like to do on videos I enjoy is to play a clicker game on YouTube that is specifically built in for you guys. It is called the like button, so make sure you click that like button so it turns blue, and you are officially partaking in the clicker game. Anyway, jokes aside guys, if you're new around here, make sure you subscribe to my channel and turn on the bell for future notifications of videos and live shares. Let's just hop right into it. Before we get started with the programming, I want to head to our edit and then project settings and we're going to head to player. We're going to change our company name to whatever you want. So for me, it's just going to be CryptoGround for you guys. It's whatever you want. Just make sure you stick to a name and you're good to go. So one thing about the company name here is that we're going to be using this to store our files. So if you were to change this on an update, it's going to lose its location and that will not be good. So make sure you choose a name and you stick with it. So I'm going to be using CryptoGround. We're currently in the app data folder, local low CryptoGround. So that is the company name here. And these are all the games I use to store under CryptoGround. So I have idle research. I have crypto clickers. I have the idle game tutorial series. So this is the old one. So I can probably delete this, but we're going to store them in here. So let's look at idle research, for example, because we're going to be using a very similar save system. So we have quite a few things in here. We can ignore the unity in the player log folders, except I do want to say something quick about this. If you ever run into errors, you can see that they exist in player.log. And that is pretty cool. If you ever find something wrong with your game and you want to see if there's any errors going on in the background, player.log is the place to go. When we have our save system, we're going to create two folders, saves and backups. So for me, I have several texts for each data, but we're only going to be using one in this video. So I have the saves and I also have a backups one, which I don't have any backups currently. So what it's going to do is on save, it's going to save to our text documents, a bunch of data in here. Except we're not going to be doing any encryption because it's not necessary. I mean, you're saving and loading an idle game. And if you're trying to save something very important, they don't want people to access and that's when you encrypt it, but we're not going to do any encryption in this video. So now we know how that works. We're going to be creating a brand new scripts inside of our script folder and we're going to call it the save system. So we currently don't need these first two methods. We can get rid of those. And now part one. And now this is part one, the save system. So the first thing we're going to make is our save player method. And this is going to be static because we want to be able to access this anywhere without having to initialize save system. So we're not going to be creating save system as an object, but it's going to be a class that we're going to be able to call anywhere. So to do that, we're going to be doing public static void save data. And we're going to be adding a generic here. So the reason why we want to do that is because we want to take in any form of data object because what if we have a different data name? Or maybe we have two different types of data is like we have our game data, our settings data and maybe in that purchases data. So we want to be able to take in any form of data that we want to do. So in our case, it's going to be data right here. So we'll just leave it as a generic and the data will take in is T for generic and we're going to label that as data. And we're going to take in the file name and that would just be string file name. The first thing we want to do in our save data is to make sure these directories actually exist because right now, obviously it doesn't. So we need to make sure they exist. And if they don't, we need to create them. And if they do, then we just won't do anything about it. So to do that, we're going to be accessing the directory class. And this will be under system.io. So make sure to import the system.io name space at the top. And then we're going to do directory dot create directory. So create directory will create a directory. I know I've said it a lot. It will create a directory, basically a folder inside of a path if it doesn't exist. So we're going to take in our folder path, which we actually don't have yet. So so what we're going to do up here is create a private static string. And this will be our safe path. This is where everything is going to go. So in our safe path, in order to access the folder that we were showing earlier, we need to use something called application dot persistent data path. So this will get access to that crypto grounds folder. I was explaining earlier. And again, that is based on your company name. And we're going to have our saves folder. And it's not just saves. We're going to have our forward slash forward slash. Next, we're going to do the exact same thing for our backup path. Okay, so now we can use these paths to create two directories, one for the saves and one for the backups. So so far when we call this method, we're going to be creating two directories saves and backups if they don't exist. Now inside of this method, I'm going to create a local method called save. So the reason why we're doing this is because we have our safe path in our backup path. So I just want to call a local method just to pass through our save paths as a string argument. And in that case, we're going to be adding a string parameter called path. So in order to write to our files inside of this local method here, we're going to be using something called stream writer. And when we create a stream writer object, we eventually have to dispose it, we have to close it. And the reason why I have to do this is because it's basically the program is getting access to write to file. And if we don't close that, it's going to remain open. And whenever something else tries to read it, which we do in the future loading, then it will cause a bunch of errors. And we don't have permission to open that. So we do this in a simple way using the using statements. So it's just using and then in here we create this stream writer object. And then stream writer, we're going to pass through our path file name in dot text. And for dot text, I'm going to create a constant, which basically cannot be changed once it's initialized. And I also don't want to write out dot text every time. So I'm just going to create a constant real quick. And the thing with constants is that they cannot be static. So in order to make a constant use the constant term, and now we can pass through what we want. So we'll do path plus file name plus file type. Next inside of this using statement, we're going to create a binary formatter. So what the binary formatter does is that it basically takes all your data, it serializes it into a bunch of zero and ones. And then it can also deserialize it from zero and one back to your data. And in order to use this, you need to have the system dot runtime dot serialization dot formatters dot binary namespace included at the top. Yes, it's very long. I know it's a lot of typing. So next we're going to create a memory stream object. So memory stream. So basically a stream is a byte array and memory. And we're going to be taking a bunch of data and we're going to be storing it inside of our memory as a bunch of bytes. So byte is a data type like that. So that's pretty much what memory stream is. If you want to do more research on what a memory stream is, Google is your friend. So we have created all the objects that we need to create. Next we need to take in our data and serialize it into our memory stream. So all we got to do is formatter dot serialize. And we're going to throw in our memory stream and then our data. So next we need to convert this memory stream into a byte array. And we're going to turn that into a string. So we're going to make a string and we're going to call that data to save or whatever you want. And what we're going to do is convert it. So we're going to access the convert class, which requires the system namespace. And we're going to use the two base 64 string. And inside here we take in the memory stream and convert it to an array. Last but not least, we can finally use our stream writer to write to our file. It's very simple. You just do writer dot write line and you throw in our data to save. Perfect. So this is all we need to do for our save. Now we have to actually use it for our path. So first of all, all you got to call is save and then throw in your save path. Now we're going to do our backup save. How often do we want to save our backup files? I'm saying every five times. So I'm going to create a private static int and this will be our safe count. And every time we save our data, we're going to add this by one. And every five times we're going to call the save, but with our backup save path. And then we're going to also save to our normal just in case. So if the safe count mod five is equal to zero, we're going to call save backup save path. I think I've explained this in the path, but just to refresh what mod is, it's basically the remainder. So if you do for mod five, you get four. If you do five, my five, you get zero. 10, you get zero. 12, you get two. So it's just the remainder of 12 divided by five. Two is the remainder in our safe system is good to go. And that concludes for part one. Now we're on the part two. All right, guys, welcome to part two. So this is very similar to our safe system. Except we're doing things backwards. We're actually taking in the data from the file and injecting it into our game. Instead of taking our data from the game into our files. So we're pretty much doing the opposite, but it's almost the same thing. So let's start with this load data method. So it's not going to be a void method. It's going to be an actually a generic method because we want to return our data. So all right, so let's make that load data method and don't forget in order to use generics. You must include this and we're returning a generic. And all we need to take in is our file name. So same drills above. We need to create the two directories and I'm going to create a temporary return value. And at some point we are going to return that data to return. I'm going to create another local method and this will be called load. And we're going to take in our path. So using the same using statements system here and instead of using stream writer, we're going to be using stream reader because we are reading from a file. So I'm going to. So I'm going to create this using statements and I'm going to create that stream reader object. Okay, so we're passing through the same path path plus file name plus file type and we're good to go. We're going to do the same for matter. And before we create this memory stream, we need to read from the file first. So I'm going to create a string and to read the file, we're going to be doing reader dot read to end. So it's going to read the entire file and it's going to throw it into the string. So now what we're going to do with this memory stream is that currently we're using the empty constructor. However, there is another constructor where we can pass through a byte array. So currently our data is saved as a byte array string. So this is the array and then we just convert it into a string. And how do we know that? Well, if I were to hover over this, it says that this method takes in a byte array. So yes, this is a byte array and we convert this into a string. So now we're going to be doing the opposite. We're going to be converting this string into a byte array. So to do that, we're going to be using convert dot from base 64 string. And we're going to throw in our data to load. And that will be a string. And that is all we need to do there. Next, we're going to take this memory stream. We're going to deserialize it. So again, we're basically doing the opposite of this. So we're basically going backwards. So we're going to deserialize this and then return the data. So we're going to set our data to return equal to formatter dot deserialize. And we're going to put in our memory stream. Let's see, what is this area here? Cannot convert source type object to target type generic. So we need to cast this as a generic. This should work. However, our backup. So what if this doesn't work for some reason? What if this save file, this normal one is corrupted? We're going to be using our try catch statements just to determine if we need to use the backup method instead. So I'm going to drag this line into the try. And then I'm going to create a bull up here. And this would be called a backup needed. And basically if we get any form of error when trying to load this, we are going to set this backup needed equal to true. And just in case the backup doesn't work, we're going to set data to return to default. So default is basically a full reset. So that is basically just creating a brand new data here. It's the same thing. Okay. So once again, we're going to create a binary formatter. We're going to read the file and add it to a string. We're going to create a memory stream, throw in a bite array, and we're going to convert that string into a bite array. So we can put it in the outside of our memory stream. And then we're going to try to deserialize this memory stream and cast it as our generic, which will be our data. And if that fails, we're going to set backup needed equal to true. And data will return to a brand new data object just in case the backup fails. So now let's actually get this going. So first we're going to be using load save path. And then we're going to have an if statements. If backup needed is true, we're going to be calling load but throw in our backup. And that is all this load data really is. And that is all this load really is. It's a little complicated at first. I definitely had a hard time understanding it. But if you were to do extra research and take a look at the Microsoft docs, you will start to understand some of these terms much better. And I'm going to be honest with you guys and shout out to Microsoft. You guys made the best docs ever. They're really great. This concludes part two and on to part three. Welcome to part three guys. So this will be checking to see if this save file actually exists. And this is a very short method. So I'm going to be creating a public static bull because we're going to determine whether if it exists or not. And same thing as before, we're going to be taking in a file name string. Okay. So in this save exists, we're going to create an if statement. So if file dot exists, and this will be our save path plus file name plus file type. So if this exists, we're going to return true. Otherwise, if it doesn't exist, we're going to check to see if our backup exists. And if it does return true, otherwise return false. So we can simplify this a little. First of all, I can just get rid of this return false. And I can get rid of this true. And instead, since this is a bull, let's just return this. So if the backup exists, we're just going to return whether it does or not. And another thing we can do is also merge these two. So what we can do is return this or this. So if one of these exists, we're going to return true just like that. And I'm going to convert that into an expression body. And it looks pretty neat. And you want to use the save path first before the backup save path because it's going to check for this one first, and then it's going to return true. If it does exist, we don't want to keep loading up our backup file first. And make sure you have using file equals unityengine.windows.file at top. This will be for your file that exists. And that concludes for part three on to part four. So part four is actually implementing this. So first, I want to start with our data. Now remember how we were serializing and deserializing stuff earlier? Well, we need to add an attribute called a serializable. And this basically just tells us that, okay, we can turn this entire class into a bunch of zero and ones. So we're just going to mark it as serializable. Now in our controller, instead of this right here, we're going to be loading our data. So I'm going to get rid of this and we're going to set data equal to save system dot save exists. And we're going to check for our file name. I'm going to name this player data underscore tutorial. You can name this whatever you want. Just at least you know what it's called just in case you need to do any debugging or finding the file later on. Okay, so now we're going to be checking for player data underscore tutorial and don't add the dot text. Okay, just leave it as the name. And so if it exists, we're going to be loading this data. So save system dot load player. And inside of these right here, we're going to be putting in our data because this is the object we're going to be using. And we're going to be looking for that same file name. So you can make this a string, a constant and use this for everywhere else if you want. I'm going to do that because why not? So I'm just going to make it constant. And then I'm just going to replace these. If the file exists, we're going to load our data. Otherwise, if it doesn't exist, we're going to be creating a brand new data. So it's just going to be empty. It's going to be a full reset. And usually when you start the game, that's just how it's going to be. And now we're going to be implementing the save system. So I'm going to create a float save timer. And inside of this update method, I'm going to be adding it by time dot delta time times one divided by time dot time scale. And the reason is because if you ever decide to speed up the game for some reason, if you decide to add a two times game speed boost or something like that, it's basically going to ignore that. And it's just always going to increment by one second. So now we're every 15 seconds is when we're going to save it. You can do five seconds. You can do 20 seconds. You can do a minute, whatever you want. Or you can even add settings to change this if you'd like, but we're just going to be doing 15 seconds. So if the save time is greater than or equal than 15 seconds, we're going to call save system dot save data. And we're going to put in our data and then the file name data file name just like that. And we're going to reset this timer just like that. Now shall we test? Oh, okay. So one thing I forgot in the save system is that I forgot to set this to false. It's because it's unassigned. So make sure you set this back up needed to false. All right. So we don't need to assign anything inside so we can just hop right in. We're good. Let's make some progress and we can watch that save time or go up. Let's buy some upgrades. I think a production one will be good. There we go. Let's wait for that to save. And we're good. Okay. So now let's check where this is going. So if we go back to our crypto grounds or wherever you saved it. And if you don't see the app data anywhere, you can just type here, you can just go to the search, do percent app data percent. And it brings you to the app data folder. And it brings you to roaming. So you'll want to go back and then to local low. That's where this is. And you can see I will give tutorial series perfect. So it is inside here. And we have our two folders saves and backups. So one silly mistake is that I named this dot text instead of dot txt. That is my bad, but it's not the it's it's it's okay if that happens. I'm just going to rename that. So obviously we're going to not be able to load our data. But I guess I can show you what will happen, but we can still edit this file and we can see all of our data here. So all of our data has been serialized into this massive clump of crap. So can't really read it unless you grab this, put it into a new C sharp script and and deserialize it from there. Otherwise to a newbie and average player, they're not going to know. And if someone's cheating in your game, don't waste don't waste time solving those issues. You have a lot of better things you could be doing. So anyways, I'm going to restart this because I changed the file name to dot txt instead of dot text. So let me do that real quick. Okay, so now we can see this new file here. And again, it's the exact same thing. So let us exit and go back and it should load our data. Cool. So we have our our upgrades and we have our flash and our production. And that's it. That is all we need to do. And I'm going to leave this in the comments below just in case you guys get lost in case you guys get some errors or you missed type something. Who knows? I'm just going to leave that in the comments below. So anyways, guys, if this video was helpful and if you enjoyed it, make sure you click that like button. Once again, you got to be part of the game. Subscribe to my channel if you're new around here and turn on the bell for future notifications of videos and live streams. Personally, I'm very satisfied with this video. I think it's structured a lot better than the previous videos and I hope you guys agreed with that as well. If you guys want me to continue this structure, leave a comment below and let me know what you guys think. I hope you guys have a great day or night. Thank you guys for watching and I'll see you in episode 4.1 which will be exporting and importing. See you guys in the next one. Peace.