Save and Load Game Data

Saving and loading game data is essential for games like Val’s Revenge. Players don’t want to spend a lot of time progressing through a game if their progress isn’t saved.

But saving and loading game data isn’t something you can take lightly. You need to consider what data to save and how to save it.

For a game like Val’s Revenge, you’ll need to save the current level, the number of keys collected, and how much treasure the player found. Because you don’t want nefarious users to “break into” this saved data and make changes, you will securely store it to the file system rather than using NSUserDefaults.[44]

To help speed things up, you’ll be adding a pre-made GameData class.

Open Finder and in the resources folder for this chapter, you’ll see a GameData.swift file. Drag that file into the Project Navigator, placing it just below the GameViewController.swift file. Don’t forget to verify that the Copy items if needed and Add to targets options are both checked. Also make sure that the option to Create groups is selected.

The GameData.swift file is small, but it packs a lot of punch. If you don’t already have it open in the Source Editor, open it now.

First, notice that the GameData class contains the following properties (ignoring the static property for a moment):

 // MARK: - Properties
 var​ level: ​Int​ = 1
 
 var​ keys: ​Int​ = 0
 var​ treasure: ​Int​ = 0

These three properties are, essentially, all you need to worry about when saving and restoring the player’s game.

Saving and restoring game data doesn’t have to be painstaking—especially, if that data supports the Encodable and Decodable protocols; something you can specify by using the Codable type alias.[45] However, because the Player class itself does not conform to these two protocols, and making it do so would be quite the undertaking, you’re creating a smaller, more focused class to handle only the game data your player needs. For more information on the Codable type alias, please review Apple’s online documentation.[46]

Below those three properties, you will see a static property with the following code:

 // Set up a shared instance of GameData
 static​ ​let​ shared: ​GameData​ = {
 let​ instance = ​GameData​()
 
 return​ instance
 }()

This code sets up the shared instance property. You’ll use this property to access the other property values, such as GameData.shared.keys.

Next, you’ll see the methods that make up the GameData class. The two key methods are saveDataWithFileName(_:) and loadDataWithFileName(_:). One method is responsible for writing the data, and the other method is responsible for reading that data.

Here’s how it works:

The saveDataWithFileName(_:) method converts the GameData object into a file that looks something like this:

images/AddingMoreScenesAndSavingTheGame/raw-json.png

What’s nice about storing the data using NSKeyedArchiver is that the data is unreadable by humans, because (in reality) the raw file looks something more like this:

images/AddingMoreScenesAndSavingTheGame/raw-json-hidden.png

By storing the game data in this format, you lesson the risk of nefarious players cracking that data and increasing their stats.

Now, take a closer look at the loadDataWithFileName(_:) method. Inside of that method, you’ll see (in part) the following code:

 let​ gd = ​try​ ​PropertyListDecoder​().​decode​(​GameData​.​self​, from: data)
 
 // Restore data (properties)
 level = gd.level
 
 keys = gd.keys
 treasure = gd.treasure

This code is where the stored values get read back into the GameData object’s properties. This processing happens after the code uses NSKeyedUnarchiver to re-assemble the data.

You’re welcome to look around the rest of the file; however, before you can use the GameData class, you first need to modify the AppDelegate.swift file.

Updating the App Delegate to Load Data

Switch to the AppDelegate.swift file, and in the application(_:didFinishLaunchingWithOptions:) method, just before the line that reads return true, add the following line of code:

 GameData​.shared.​loadDataWithFileName​(​"gamedata.json"​)

This code uses the shared instance of the GameData object to load the player’s game data. Now that you have a way to load that data, you’re ready to use that data to populate the keys and treasure properties in the Player class.

Updating the Player Class to Use Saved Data

Switch to the Player.swift file and update the keys and treasure properties to use the data from the GameData object as their default values, like so:

 private​ ​var​ keys: ​Int​ = ​GameData​.shared.keys {
 didSet​ {
  keysLabel.text = ​"Keys: ​​(​keys​)​​"
 if​ keys < 1 {
  stateMachine.​enter​(​PlayerHasNoKeyState​.​self​)
  } ​else​ {
  stateMachine.​enter​(​PlayerHasKeyState​.​self​)
  }
  }
 }
 
 private​ ​var​ treasure: ​Int​ = ​GameData​.shared.treasure {
 didSet​ {
  treasureLabel.text = ​"Treasure: ​​(​treasure​)​​"
  }
 }

Perfect, the properties are now using the stored data as their defaults, which will come in handy when you load your game scenes; however, because these properties are private, you need a public method to access them, so add the following code, placing it just above the init(coder:) method:

 func​ ​getStats​() -> (keys: ​Int​, treasure: ​Int​) {
 return​ (​self​.keys, ​self​.treasure)
 }

With this method, you’re returning both the keys property and the treasure property, both of which are part of the Player class.

The next step is to modify the startNewGame() and resumeSavedGame() methods to keep these properties up to date.

Modifying the Scene Manager

Switch to the SKScene+SceneManager.swift file and update the startNewGame() and resumeSavedGame() to match the following:

 func​ ​startNewGame​() {
 // Reset saved game data
 GameData​.shared.level = 1
 
 GameData​.shared.keys = 0
 GameData​.shared.treasure = 0
 
 // Load level
 loadSceneForLevel​(​GameData​.shared.level)
 }
 
 func​ ​resumeSavedGame​() {
 loadSceneForLevel​(​GameData​.shared.level)
 }

In the first method, you’re “resetting” the values stored in the GameData object when the player starts a new game. In the second method, you’re “using” one of the properties to resume a previously saved game.

Finally, switch to the GameScene.swift, and in the sceneDidLoad() method, add the following code:

 GameData​.shared.​saveDataWithFileName​(​"gamedata.json"​)

This codes triggers the method that saves the data, and it does so each time players advance to the next level, making it possible for players to “try again” should they fail to complete the level.

Before you can test the new saving and loading routine, you first need to:

  • Add a way for players to get to the next level.
  • Create and add more levels.

Let’s start by adding a way for players to get to the next level.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset