Lesson 17.1: Saving and loading game state

We have enough features in the game that it could be worth playing – especially if you made your own world larger by adding your own monsters, locations, quests, and game items.

So, to make the game more playable, we will add the ability to save and load the game.

We currently save the game data in XML files, but I switched to JSON files for the saved game data. JSON is more popular than XML nowadays, and it’s something you’ll probably see in other programs – especially if you work with REST APIs.

 

 

 

 

Lesson Steps

Step 1: Create \Engine\Services\SaveGameService.cs

The first thing we’ll create is the SaveGameService, to save and load the game to disk.

The format is JSON (JavaScript Object Notation), which is like XML. I have a sample saved game below, so you can see what JSON looks like.

The main differences from XML are changing XML nodes and values to use key/value pairs, using curly braces instead of brackets, and not using closing nodes (since the closing curly brace identifies the end of an object).

 

In order to work with JSON, I included a NuGet package named Newtonsoft.Json. A NuGet package is a library of code, like a DLL you’d create from your own projects. In fact, you can create your own NuGet packages if you have some re-usable code you want to share (even if only with yourself). Using NuGet package manager, you can add a library to your projects, and to keep them updated as new versions are released.

 

To add a NuGet package, right-click on your solution in Solution Explorer and select “Manage NuGet Packages for Solution…”. That will display a screen where you can search for packages online and manage the packages in your solution.

Click on the “Browse” link and type “newtonsoft.json” into the search textbox. Left-click on the Newtonsoft.Json package. On the right side of the screen, you’ll see a list of your projects. Select the “Engine” project to add Newtonsoft to, select the most recent version from the dropdown, and click the “Install” button. This adds the package to your project. Now we can use the Newtonsoft JSON functions in our project.

 

NuGet Package Manage screen in Visual Studio

 

Now we can get to the SaveGameService code.

Notice the “using” directives on lines 1-7. We need “Sytem.IO” to read and write files, and we need two Newtonsoft.Json namespaces, so we can use the JSON classes and functions.

On line 13 we have a constant to store the saved game’s file name. In the future, we could expand this to allow multiple saves. But we’ll just have one saved game for now.

Lines 15-19 are the Save function. We pass the GameSession object into the function and serialize it into a JSON string with:

“JsonConvert.SerializeObject(gameSession, Formatting.Indented)”

This function looks at the properties of the gameSession object (and, if the property is a class, that property’s properties) and converts them to a string. The File.WriteAllText function writes that string to the disk.

Lines 21-48 is the function that loads the last saved game if one exists. If a saved game file doesn’t exist, the function will return a new GameSession object instead.

If the file existed, line 31 reads it from disk and puts the JSON into a JObject – an object we can query against to get values.

Line 34 passes the JObject to the CreatePlayer function, which will call all the other code so we can populate the GameSession object with the player data.

On lines 36 and 37 we get the last saved game’s location X and Y values. We get the values like you would get a value from a multi-dimensional array – except, instead of using numbers for the indexes, we use the names of the properties.

Notice we always use nameof() to get the property name. This way, if we change a property name in the future, either the property name here will change (if we use a refactoring tool to rename the property) or give an error – which we should quickly notice.

On line 40 we instantiate a new GameSession object with the data we read from the saved game file. This uses a new GameSession constructor we’ll create later.

On lines 42-47, if there was an error while parsing the saved game file, we create a new empty GameSession object and return that.

Lines 50-149 are the functions to parse the data in the saved game file.

Something to notice is that they all get the file version from the saved game file before doing the parsing. This is because we will probably change the properties in GameSession and its properties’ classes. Since different versions of the saved game file will have different formats, they’ll need to have different parsers.

Other than that, we basically parse the values from the file and used them to construct the other objects we need – Player, GameItems, Quests, and Recipes.

The final function on line 152-155 gets the version of the saved game file.

 

SOSCSRPG.json

 

 

SaveGameService.cs

 

 

Step 2: Modify \Engine\ViewModels\GameSession.cs

There are only a few changes to GameSession.

First, I moved the backing variable _currentBattle from line 12 to 16, so it can be with the other backing variables.

On line 20, there’s a new Version property. It’s a “get”-only function that returns the hard-coded version number. If we change the properties of the GameSession class, we’ll need to update this with a new version number.

Next, we need to create the new GameSession constructor to use when passing in saved game data.

In the original constructor, I moved the line where we populate the CurrentWorld property from line 137 to line 123. It wasn’t required. But it feels more natural to populate the world before we do anything else.

Then, we have the new constructor on lines 143-148. It takes a player and the X and Y coordinates of where to start the player and populates GameSession’s properties.

 

GameSession.cs

 

 

Step 3: Modify \WPFUI\MainWindow.xaml

Now that we have code to save and load a game, we need to modify the UI to use it.

Add the “Closing” attribute on line 12 of the window’s attributes, with the function to run when the window closes.

 

MainWindow.xaml (lines 1 – 12)

 

 

Step 4: Modify \WPFUI\MainWindow.xaml.cs

On line 3, add “using System.ComponentModel;”, which we need for the Closing event arguments parameter.

Change line 18 to only declare the private GameSession variable, instead of instantiating a new object for it.

In the constructor, on line 30, set “_gameSession” to whatever value is returned from the SaveGameService.LoadLastSaveOrCreateNew function. This is how we load the game from the saved game file (if it exists).

Lines 125-128 is where we have the function for the window’s Closing event, and where we call the function to save the current game to the file.

 

 

MainWindow.xaml.cs

 

 

Step 5: Test the game

You should be able to start the game, change the player somehow (move to a new location, get loot from a monster, etc.), exit the game, restart the game, and see the player in the same state they were at when you exited.

You can also edit the SOSCSRPG.json file (maybe change the player’s dexterity), start the game, and see the changed value.

 

Summary

This is a good feature to have in the game. But, if we change the properties of the Model classes (or their factories), we’ll need to remember to change the code in SaveGameService.cs. When we do this, we also need to remember to change the version number and write new code to parse the new format.

 

 

Additional links for this project

Source code: https://github.com/ScottLilly/SOSCSRPG

Project plan: https://github.com/ScottLilly/SOSCSRPG/projects/1

Discord: https://discord.gg/AUYXYtH

Return to main page

Leave a Reply

Your email address will not be published. Required fields are marked *