Unless you're making a retro game and trying to infuriate players by throwing them back into the era before hard drives or memory cards, you're probably going to want to let your players save their progress in your game. If you're working in Unity, you've probably searched for ways to write a save system and found numerous results showing you to use the built in Player Press feature. You've then probably realised that this is fine for small games or projects that don't need to store a lot of data, but if like me, you're building a fairly complex game with lots of data you want to store, you're going to want a more functional solution than writing hundreds upon hundreds of entries into the Player Press system. So let's do it a different way. Hi there, I'm Matt and welcome to Game Dev Guide. Today, we're going to use binary formatting to build an extremely flexible save game system. The plan here is to build a custom save game class that holds all of the data we want to store. Then, when we want to save our game, we'll write our save game class to a text file. And when we want to load the game, we go the other way, casting back from the text file to our custom class. So there's three main components here. Firstly, there's our game classes that need data stored. Secondly, there's our save game class that will hold the data and finally, our serialisation system to actually handle the saving and loading of the text file. With that in mind, let's get started. The first thing we'll do is create our serialisation script. Let's create a new folder called serialisation and a new C-shop class called serialisation manager. The first thing we'll do is write our save method. This will take in a save name string and the object we want to save. We'll also return a Boolean to confirm that the save was successful. And a separate method will define our binary formatter. This method will create will eventually allow us to define serialisation surrogates for our formatter to use. More on that a bit later, for now though, we'll just return our binary formatter. Back in our save method, we'll define the path for our save game. You could define anywhere on the system, but I'm a fan of using the persistent data path and creating a save folder in there with the save name and a custom extension, which we'll just call .save for now. Then we'll create a file stream at our path and use our binary formatter to serialise the object. Then close the file stream. And that's the saving handled. We'll write another static method called load, which we'll take in a path to load from. We'll then check that the path exists, get our binary formatter again and open a file stream at our path. We'll attempt to open the file. And if it works, we'll return the saved object. And just in case we've accidentally passed the wrong path into our loading system, we'll make sure our whole codebase doesn't implode by catching it with an error and returning a null object instead. With that finished, let's move on to the star of our show, our save data class. Let's create a new C-shop class and call it save data. We'll make it a singleton and create a new reference to it if one doesn't already exist. The key to our save system working is that classes we want saved are marked as serializable. We do this by adding the serializable attribute to the class header. Anything within our save file should be serializable by the binary formatter. So any additional classes we create that wish to have their data stored will also need to be marked as serializable. To demonstrate this and to test if our save system works, let's create a new class called player profile. This will hold some information about the player, such as their profile name, how much money they currently have, and their experience level. In our save data class, we'll create a public player profile called profile, and we'll also add some resources. Let's suppose the player can buy toy cars and toy dolls with their currency, and we want to keep track of how many of each toy they have. To test this out, I built some UI where the player can either click a button to start a new game or load a previous save. If they start a new game, they are asked to input their name, and this is saved to the profile data as well as some default variables. They're then taken to a second screen, which shows their name and has a button to buy toys or work. If they click the work button, they gain some currency and experience, if they buy toys, they spend some of the currency and gain a toy. As you can see, we're tracking this data in the UI. In any of these buttons are pressed, we're updating the data in our save system. If I choose to save, make some more changes, and then hit the load button to go back to that save, the load button simply replaces the save data class and tells our UI to update, restoring the state from our save. And the same thing happens if we restart the game and choose to load. So that's great, it looks like our save system works. But we're not done yet. Right. So as you can see, I've taken the toy box analogy further. Our prototype has developed a bit now, and we've got toys we can spawn and move around. I want our save system to remember which exact toy to save and where it is in our play space, so things are a bit more complicated. The binary formatted doesn't support Unity classes, such as GameObjects, Transforms, or Vector3s out of the box, which means that we can't just create a list of GameObjects and expect our system to save them. Instead, I've created a custom class for our toys and added a list of them to our save data. This class is marked as serializable, and it has an enum for the toy type, a string ID, Vector3 for its position, and a Quaternion for its rotation. We give it an ID so that any other object that relies on it can get a reference to it upon loading again. You're probably also wondering why I'm storing position and rotation when I literally just told you that both of these things aren't serializable by the system. Well, that's because we're going to need the serialization surrogates I mentioned earlier. A serialization surrogate is essentially used to tell our binary formatted how to serialize classes that aren't natively serializable. So, for the case of Vector3s and Quaternions, we need to create a serialization surrogate. We do this using the i-surrealization surrogate interface, and then tell our interface how to handle the data for each type. We then add this to our list of surrogates, and tell our binary formatted to use them. It's worth noting you could probably extend this further and create surrogates for all of the properties of a monobahavior to make it completely serializable, but I try to limit the data I handle as much as possible. Anyway, with our setup, let's tell our toys to add themselves to our save data when they're spawned if they don't already exist. We'll also make the game object destroy itself when the load event is called, in case it needs to be replaced. And to update their data when they're interacted with. Then when we load, we'll tell our game to go through the data set and spawn each toy, based on its enum, placing it in its save position and rotation. If we try it out, you can see our system successfully saves and loads our more complex toy data thanks to our surrogates. The only problem with our current system is that it's pretty old school as it only handles a single save file at a time. We ideally want to support multiple save files. Fortunately, that's actually a pretty straightforward modification to make to our system. Let's create a save manager class. This class will act as a buffer between saving and loading our game. In our save manager, we'll tell it to get a list of all of the files at a specific directory and store them in an array. Then all we need to do is build a UI for it like so. When the player saves, they're asked to define a save name which our save manager uses to save the game. When they choose to load, the save manager displays the list of files from the directory in the UI. When the player chooses the item in the list, we call the load method passing in the index of the item they've selected. And there you have it, easy, multi-file saving with just a few more simple steps. As you can see, this is an extremely useful and flexible way to handle saving in your game. You're mostly just redesigning the way that you handle data management in your code base rather than writing numerous lines of code for each individual data point you want to save. So hopefully there's something useful in there for you. Let me know if you've learned anything new and how you might use a system like this in your game below. Also, be sure to subscribe for more Game Dev tips, tricks and tutorials. And if you're interested, why not check out some of these other videos across the channel. As always, thank you very much for watching and I'll see you again next time.