Hi, everyone. I'm Jeremy Gibson Bond and welcome back to the Unity Certified Programmer exam review material. In this video, we're going to be looking at the solution to the local save challenge that I gave you in the last video. So let's start by looking at the DeleteSave button. You can see here that in screen space overlay, I'm going to hide these two panels that are on top of it and then here in the title screen panel is the DeleteSave button. A couple of new things here, you can see that now, active only during some game states has an editor only boolean, and that means that when that is checked, that button will appear exclusively in the Unity editor. So, players will never see this and we don't have to worry about removing it from our game later. Let's see how it works. You can see here that when I click the button, it goes to the title screen script that is attached to the title screen panel and calls DeletesaveFile there. Let's look at how that works. Here we are in Visual Studio and you can see there's a new public method, DeleteSave, and all this does is then called the static method DeleteSave on SaveGameManager. So, this works just like the start game method that we used for the Start Game button before. Now, let's take a look at the SaveGameManager. You can see that this is a static class, it declared on line nine. So, this means you don't have multiple instances of it. Just like a static field, there's only one instance of the static class. It can have static fields like saveFile and file path that you can see there, as well as, the lock boolean that we'll get to in a moment. You can also have what's called a static constructor. On line 24, you can see the static constructor for the SaveGameManager. This is called the first time any script references SaveGameManager and it sets up our static class instance. So, it initially sets the lock to false. This lock boolean will stop us from saving the file when it is locked. This allows us to do things in loading a file that might normally cause a save to happen and it stops the save from happening. It's just a way of protecting us from messing up the integrity of our saveFile. Then I set the file path which is this persistent datapath plus AsteraX.save. So, AsteraX.save is just the name of our file, but this application that persistent data path is valid on all the platforms we're going to use and it is a hidden place locally that Unity can saveFiles on the device. This is better than PlayerPrefs now because a lot of players have figured out how to hack PlayerPrefs and this gives us a little bit more secure place to put things and because we have direct control over what we write into the saveFile, you can encrypt it, you can do all sorts of things, whatever you want to make it as secure as you can. Then you've got my first instance of DEBUG_VerboseConsoleLogging. So, I have this turned on now and what this does is it just gives me more information about what's going on with the saveFile while in developing and then as with a lot of these things, I can just comment it out at the top and then it will not be defined in all of these lines in between the if and inif, will be commented out and will disappear from my script so that I don't have to have all of these debug messages popping up all the time. The final thing that I do in the constructor is I create an instance of the saveFile class and I store it in the static field saveFile. Let's go take a look at that class here you can see the definition of the saveFile class at the bottom of the SaveGameManager script and it's a pretty straightforward class. It has arrays for StepRecords and achievements that just duplicate those arrays in the achievement manager and then it has an integer to store the highScore and it has a base highScore of 5,000. Now, let's look at how that high score is managed. You can see here that I'm in the AsteraX script in the addScore method. This is the one that gets called whenever we say shoot an asteroid. So if you shot a small asteroid which is worth 400 points, then AddScore would be called with the number 400. It goes through and adds the score like normal and then on line 573, we say if we have not already gotten a high score in this game, and we check the highScore in the SaveGameManager and it is higher than our current high score, then set GOT_HIGH_SCORE to true and show a pop-up about it. So, let's go back to the SaveGameManager and look at CheckHighScore. You can see that this just says, if the score that was passed in is greater than the saved high score, set saveFile to highScore to the new score and return true. Otherwise, return false. Back here in the AsteraX script, you can see that we only check for a high score until we know that we've gotten the high score and then once GOT_HIGH_SCORE is set to true, we don't check anymore. This is to prevent us from constantly checking and constantly updating and things like that. So, I'll show you how we manage getting the final score in a moment. But first, I wanted to talk about this AchievementPopUp.ShowPopUp. This is a new static method on AchievementPopUp that allows us to cause that drop down to happen anytime we want and it's very straightforward. You can see here in the AchievementPopUp script that ShowPopUp just takes an achievementName and an achievementDescription which is respectively the top line and the bottom line of that pop up and then it calls the singleton S.PopUp and passes those in. So this is a way of using the AchievementPopUp without passing in an actual achievement or having a reference to it. So it makes it able to be used in several different ways in our game if we want to. Going back to AsteraX, I talked about how I'd handle actually getting the high score when the game is over, and that is exactly it. We wait until the game is over and then as part of our GameOver, we check for the highScore and pass in the final score in the game which is going to take the highest score in the saveFile and up it to the actual highScore and then we call SaveGameManager.Save to save the file, and here is that save method inside of the static SaveGameManager class. So you can see that if the class is locked, I return immediately. So I do not save if I've lock set to true. But if lock is not set to true, I pull the stepRecords and the achievements from the AchievementManager, then I pass that instance of the saveFile class into JsonUtility.ToJson. With a second parameter of true, this converts the saveFile from a class into JSON text into a string and the true is pretty print. So that makes it to where it's easier for a human to read. After converting to a JSON string, we then write that string to the filePath that we recorded earlier using the File.WriteAllText method and it is line six using system.IO that allows us to access the file class and write to files. Finally, if we had the VerboseConsoleLogging turned on, we will spit out both the filePath and the JSONSaveFile text to the console in Unity. So, we can see what's going on. So, let's take a look at that right now. If you're following along, remember to re-enable the Level AdvancePanel and the GameOverPanel. Otherwise, you'll run into some errors. So now, I'm going to save my scene and I'm going to press play and then I'm going to click DeleteSave which will wipe out my saveFile and click Start. Let's get a couple of achievements. So, those are the first two achievements and you can see that each time I got an achievement, I saved, which is the other time that we save the file. One time we save is at the end of the game and the other time is anytime we get an achievement because achievements are important and we don't want a player to lose that progress. Let's take a look at what this text actually looks like. So now, we're looking at the text of the SaveGameJSON file. You can see that it has sort of JSON setup at the very beginning and then stepRecords are very easy to read. Achievements are very easy to read, and finally, we've got the highScore at the bottom. So again, this is the pretty print that makes it easy for a human to read it. Without the pretty print, it would all just kind of be one line that's wrapped around it was a little harder to read. Here we are in the AchievementManager inside of Visual Studio and you can see that on line 103 of the AchievementStep method, we have SaveGameManager.Save. So, it goes through the achievement step and says, "If I have completed an achievement, then I also need to save." So it announces the achievement completion and then it saves the file. Now that we've talked about saving, let's talk a little bit about loading. So, you can see the Load method on SaveGameManager here. First thing it does is make sure that the file exists that you're trying to read. If there's no file there because you've never actually saved a game or if it was saved under a different name or something like that, then you'll get this warning at the bottom that just says, "Hey, I wasn't able to find saveFile but that's fine, if you've never saved a file." So, let's go back up to where the file did exist. So, if the file does exist, we're going to read all the text out of that file into a string called dataAsJSON, and then we're going to try to use JSONUtility.FromJSON to convert that dataAsJSON string into an instance of the saveFile class, and that will be assigned to the saveFile instance in our SaveGameManager. If anything in that fails and calls an error, we'll get a catch that does a debug.log warning to tell us that the saveFile was in some way malformed, and then it will give us the dataAsJSON so we can actually see maybe what was wrong with the saveFile. If that all succeeds just fine, then it will tell us we successfully loaded the file and then it's going to set lock to true before having the AchievementManager load data from the saveFile, and that's just to make sure that anything we do while loading doesn't save over a new file and this is actually an issue when you get to the next challenge which is customizing the ship. Because in that challenge, every time you click a button or flip a toggle to choose a ship part, it saves that preference in the saveFile and when I was doing testing, I actually found that it would load the file and assign what part was selected and then save a new file which only had, say, the turret selected but not the body. So, that was a problem which is why I added the lock here. Here we are in the LoadDataFromSaveFile method of the AchievementManager. You can see that it takes a saveFile as its parameter and then it goes through all of the saveFile stepRecords, finds what type they have, what step type they have, and then uses that to search through the STEP_REC_DICT in each event manager, find the proper stepRecord, and then set it's num to the num in the saveFile. Then with achievements, it goes through each achievement in the saveFile and just iterates over all the achievements in the AchievementManager, finds the one with the same name, and then set its complete status to the complete status from the saveFile. This foreach loop on the bottom there with achievements is not the most efficient thing I could have written but I wanted to show you a couple of different ways to do it and frankly, there's just not that many achievements so it's not really a big deal here. Now, of course, you might be wondering, where are we actually load this file? When do we load the file? You can see here in the Start method of the AsteraX script that right after I've set up the new asteroids list and set the score to zero, then I load the SaveGameManager file. I initially had placed this load in the static constructor for SaveGameManager but that was a problem because it's a static constructor. So, it only happened once in the entire time the game was running. So, reloading the scene didn't actually cause it to reload the saveFile. So instead, I've put this inside of AsteraX.start so that every time I reload the scene which happens whenever we have a game over, it reloads the saveFile then as well. All right, so the last thing I wanted to talk about here is deleting the save. This is actually more complex than you might think. When we start the game, it loads in the saveFile so all that data is already loaded and then we have to delete it as well as reset things like the stepRecords and the achievement completion and selections of parts of the ship later on in the next challenge. So, here's the DeleteSave method that gets called on line 97 here, and first, if the file exists, it deletes the saveFile. If there's not a file, it just kind of lets you know that there was no saveFile to delete. This is not within the DebugVerboseConsoleLogging because this only is going to happen while you're debugging and I wanted you to get those messages regardless. So, the last thing that happens here is it calls AchievementManager.ClearStepsAndAchievements. Let's go look at that. So here in this method, we go through all the stepRecords and set the num on each step record to zero, and then we go through all the achievements and set completion of each achievement to false, and that's how we kind of clear everything out. All right, I hope you enjoyed this challenge. I look forward to seeing you in the next one. Thanks.