Game Design Tips #14 – [Unity + C#] Persistent Data

Today I’m gonna talk about how you can save data to disk and read it back later. Unity has a utility built-in for this – they’re called PlayerPrefs – but for anything more than perhaps options and preferences, you’re going to want a more robust system for saving data between scenes within your game or separate game sessions. This is where I jump in and throw a bunch of fancy C# things at you! Note – this system should work on standalone platforms such as Windows, Mac and Linux and mobile platforms, but it won’t work on Web Player builds or platforms such as Samsung TV. I’m not sure if it works on console platforms since I don’t have the resources to develop for any of them and the internet has been unhelpful in this regard, but oh well. Time to get started!

There is a lot of motivation for wanting to save data to disk; high scores, player progress, and player-customised assets are all types of data we want to make persistent. I’ll be showing you how to achieve this in Unity using C#. To do this, we need to package up the data we’re going to save into its own class.

tip_14-01

We’re going to put this portion of code right below the SaveStuff script, which we’ll be writing next. For the SavableObject class, we’ve made a bunch of variables that we want saving to disk. In this example, imagine there’s a guy named Bob, and that he wants to remember his own name, how many bananas he’s holding, how long he’s been dancing (measured in hours – he’s an active bloke) and whether he’s made of wood. From this, we can only assume the following: he’s forgetful, probably a fan of potassium, potentially flexible, and doesn’t remember if he’s a real boy. But my game doesn’t know that! That’s why I’m going to save all this crucial information to disk.

tip_14-02

THIS is my all-knowing, all-powerful SaveStuff script. Let’s gloss over all the important things this script is doing – I’m sure you’ll be able to fill in the rest. It may get a tad boring, but bear with me, because saving things is actually really fun. Honest.

First of all, you can see a small snippet of the SavableObject class at the bottom of the file to show you where I’d place it. It has a [Serializable] attribute (line 36) – in order to use this, we need to import the System namespace (line 2). In the Start() method, we make our new SavableObject and specify Bob’s name as “John” (turns out he’s really forgetful) and his body’s wood status to “true”. The constructor for SavableObject already specified default values for numberOfBananas and danceTimer, so we won’t have to worry about those. Oh, and System.IO is imported for use of FileStream and File, whereas System.Every.Bloody.Word.In.The.English.Language is used for BinaryFormatter. Now let’s save Bob’s status.

Here’s the part where I attempt to make C# exciting! SaveMyStuff() is now going to do a bunch of fabulous things. We make a fun little BinaryFormatter we’ll be using shortly and then create a wonderful, happy file called “myThings.dat”. One thing to note here – I was dumb when writing this example, so it should actually be “/myThings.dat” to ensure the file goes inside the folder at Application.persistentDataPath. We serialise the data from the SavableObject we made earlier (using rainbows), turning it into a binary string that an ordinary user wouldn’t be able to comprehend if they opened the file manually. We then close the file up and wait a second before calling LoadMyStuff() on our loader object (in practice, this second’s worth of waiting may be replaced by an arbitrary amount of time, perhaps even including closing the game and reopening it). Closing the file is a very important step, since C# places a ‘lock’ on the file while it’s being accessed, and trying to access it from elsewhere at the same time – while it’s locked – would be reeeeally bad. Got all that? Good! I told you C# was fun! Now let’s introduce the LoadStuff class and go over its features.

Oh, by the way, Application.persistentDataPath is a ‘default’ filepath that varies by export platform. Most games that use Unity will probably use this default path plus a bit of extra filename appended to it (as I have here). The filepath also contains the company name and game name, both determined in the PlayerSettings in the Editor (found at Edit/Project Settings/Player).

tip_14-03

This script happens to be pretty similar to SaveStuff, but does things backwards. In LoadMyStuff(), which happened to be called by SaveStuff (it doesn’t matter what called this method in practice, I just did that for convenience), I first check if the file “myThings.dat” exist. If it does, I create all the necessary things, as with SaveMyStuff(), but this time I’m opening the file instead of creating it. That’s why it was necessary to check that the file exists, because trying to open it if it didn’t would make things cry/crash/explode. Now that the file’s open, I can deserialise its contents, un-binary format it and stick it back into a SavableObject. Then all there is to do is grab the variables out of that object, plonk them all into a horrible example of how not to concatenate strings together, and watch the Console output all our data with no errors whatsoever.

tip_14-04

Congratulations, you’ve just saved to disk and loaded up that data back from disk! It’s a very useful feature you’ll find yourself using in a variety of places; from remembering player’s preferred options to keeping track of game progress, the possible uses of saving to disk are endless.

 

Advertisements