Version

Welcome to part two of this tutorial series that’s all about learning how to save data in a game!

In the first part of this series, you learned how to store your game’s data in a separate class, and save it to disk using NSCoding.

In this second and final part of the series, you’ll:

Take on cheaters and deal with them once and forever

Add iCloud capabilities to your game.

Feel free to continue coding from where you left off, or you can download the project, completed to this point.

Now enable your hacking scanners and set your ship’s auto-pilot to iCloud City!

Getting Started

So far everything is perfect in your game-data-land; you track the player achievements, store them to the disc, and you even have a pilot photo showing somewhere there!

Now, imagine this scenario coming to life before your very eyes: Peter is a top-notch Space Shooter, and he achieves a high score of 10,000 points. He then loads his game on Stan’s phone, which overwrites Stan’s game data. Since the file has no protection so now Stan’s high score is also at 10,000 points! Peter is upset because now Stan is doing a victory dance and tweeting out that he’s the king of Space Shooter.

With a simple game like Space Shooter, this problem isn’t a big deal. Well, except for Peter, but he’ll muddle through somehow. However, you can probably imagine a case where this would be a big problem, for example, if you were to send the high score to a leaderboard server.

The first thing that comes to mind when you need to add a smidgen of security to an app is to store the data in the user Keychain. However, it’s designed to store short pieces of text, like passwords, and you need to store more complicated data like photos. Luckily, there is a workaround for this. By the time you’re at the end of this tutorial, you’ll be ready to start working with it in your projects.

When you use Keychain to save the game data to the disc, you’ll also store a hash of the file contents in the Keychain. What does that mean? A malicious user could possibly read the data, and for example see the high score, but they won’t be able to tinker with anything without invalidating the hash.

It won’t take too long to implement the necessary modifications. First, download the KeychainWrapper class, which will make it easier for you to work with the device Keychain storage:

Note: The KeychainWrapper comes to you from another useful tutorial on RayWenderlich.com; check it out to learn more about the Keychain: Basic Security in iOS5, by Chris Lowe. Please note there are a couple of very small modifications to the source so that it’s a better fit for this tutorial.

Extract the contents of the zip file and copy the two resulting files to your project. You should see KeychainWrapper‘s files visible, like so:

Open RWGameData.m. At the top file, import the Keychain helper class header file.

You use keychainStringFromMatchingIdentifier: to check whether a value for the given key exists, and if so, you update it with updateKeychainValue:forIdentifier:. If not, you create new value storage for the checksum key createKeychainValue:forIdentifier:.

One part of the operation is now complete. Every time you save the game data file, you also store its checksum securely in the Keychain. Next, you’ll change the code so that it loads a stored file only when the file checksum is identical to most recently saved game. Locate this piece of code in loadInstance:

That’s more like it! Here’s what you did when you added this new code:

You generate the SHA256 hash of the decoded data and store it in checksumOfSavedFile

Then you get the most recent SHA256 hash from the Keychain and store it in checksumInKeychain. If you get decoded data, you must’ve stored its hash in the Keychain before.

Finally, you compare both checksum strings. If they are equal, you unarchive the data and return the resulting RWGameData as the result.

If the checksums don’t match – you don’t do anything. The code execution continues, and the method returns a new blank RWGameData on the next line.

Your basic security is in place! Good job!

Note: How you punish the user for tinkering with their game data file is entirely in your hands. In this tutorial, you’ll learn how to take a subtle approach by quietly deleting their high score and distance flown, but hey … you can be mischievous too and do things like show a sassy meme or alert the cheat police — any punishment you feel fits the crime.

The all time winner goes to the makers of Serious Sam 3. When the game detected that it was pirated, it spawned – midgame, no less – a player hunting indestructible pink scorpion that pretty much ended the game.

Build and run the project again. Look at that! Your high score is back to zero, but you didn’t modify your game file. Can you guess what happened? Try to figure it out. If you give up, click below for the solution.

[spoiler title=”Solution”]Your game data file was just fine. The issue is that you don’t have a saved checksum in your Keychain, so RWGameData ignored the file contents and zeroed out your high score. From now on the game will behave as expected, so no worries.[/spoiler]

Now, play a round or two to set a new high score, and check whether everything is working. What’s the highest score you can achieve?

Enabling iCloud

Unless you’ve spent the last five years living in a cave on the moon, then you know “the cloud” is all the rage these days. When you have the cloud you can: do some work on your laptop, continue working on your iPhone in the subway and then pick up where you left on your iPad during your flight; it’s all connected as long as your iOS device is connected to iCloud.

With so many iOS devices in your life that are suitable for gaming (c’mon Apple we want a gaming Apple TV already!), you’ll definitely want the ability to pick up any of your devices and continue blasting asteroids to smithereens. You’ll also want to have your pilot photo and high scores synchronized across your iOS devices, right?

Luckily, enabling iCloud for your game is straightforward. Here’s how you set your game up to take advantage of the cloud

Select the SpaceShooter project file in the project navigator and select the SpaceShooter target. Click on the Capabilities tab.

Click the ON/OFF switch to toggle its state to ON.

The settings will change to look like the following image:

Look at the project file list to make sure Xcode completed the listed steps. Look for a new entitlements file that enables iCloud capability for the game:

So far, so good! The next step is to enable key-value storage for your game; you’ll use this kind of iCloud storage to persist the game data between synchronized devices. That’s technology at its best.

Believe it or not, Apple’s iCloud service already backs up your game.

Time to put your head in the cloud. Literally

The next question is, “How do I get my handsome mug shot synced across all my devices?”

The logic behind storing the game data is a little bit more complicated. Consider the following:

The user might or might not have iCloud enabled on their devices. Therefore, local storage should be primary, with iCloud as an optional secondary storage.

The user might have played for a while, and only used disc storage. Then one sunny day they might decide to turn iCloud on, and the game will need to handle this situation properly, i.e. not delete the locally stored high score.

The user could delete the game completely and then re-install it. In this scenario you need to initialize the game data from iCloud the first time it launches.

So there’s a little more to it than just syncing to iCloud. Fortunately, for each of these scenarios there is a solution, and by the end of this tutorial you’ll know what to do to manage the most common.

In the beginning of this tutorial, you read that you’ll keep some game data in the cloud and some will save to the device. This means you’ll:

Check if the local high score is higher than the iCloud high score, and update the one in iCloud

Store the total distance flown locally, which effectively keeps track of the distance the player flies the ship on a particular device

Share the pilot photo in iCloud and monitor for changes across synced devices. This way if you take a new selfie on your iPhone, it will change on your iPad too

Here’s a recap of the final logic for storing the game data:

score

Stored only in memory; resets when a new game starts.

distance

Stored only in memory; resets when a new game starts.

totalDistance

Stored on disc; persists between app launches, and stored on single device.

highScore

Stored on disc, and always compared to iCloud. The highest number is used.

pilotPhoto

Stored on disc; stored in iCloud. The latest photo taken overwrites the one in iCloud.

So far, you have all of these in working order, except for the high score and pilot photo.

Since you’ll need to store just a couple of key-value pairs in your iCloud bucket, you’ll automatically work with the default storage. NSUbiquitousKeyValueStore is the class that provides you access to iCloud data. You can think of NSUbiquitousKeyValueStore as an NSDictionary in the cloud :]

After you get the default NSUbiquitousKeyValueStore, you use its doubleForKey:. It’s similar to what you would use with an NSCoder to get the stored high score value. Use the same constant for name of the key when archiving/unarchiving RWGameData. In the end, why not? The key name is just a string constant!

Now that you have the high score from iCloud, it’s time to compare it to the local data. Add these few lines:

The code is simple enough. If the local high score is more impressive than the one on iCloud, then it’s time to update iCloud. setDouble:forKey: sets the value for the high score key and finally a call to synchronize fires up an iCloud sync.

Note: If you have ever used NSUserDefaults you probably already feel at home. The iCloud API and device key-value storage are similar by design.

Next, scroll to the method called save and add at the end:

if([NSUbiquitousKeyValueStore defaultStore]) {
[self updateiCloud];
}

Checking if [NSUbiquitousKeyValueStore defaultStore] returns a class instance tells you whether iCloud is enabled or not. After the game data saves to the disc you check if iCloud is enabled, and if so, you call your shiny and new updateiCloud method.

That takes care of updating the high score to iCloud. Next, you’ll sync your photo.

Consider what you’re going to do for a moment. The logic for the high score is simple enough; compare two numbers and if the local one is bigger about the game updates the data on iCloud. Can you do the same for the photo? You certainly can’t compare photo sizes. What you could do is keep the last modified date of the photo separate and compare the last modification date of both photos. That could work, but there’s a better way.

You’re going to be ultra-smart about updating the photo to iCloud because you’ll implement a custom setter for the pilotPhoto.

First, you store the new image in the backing instance variable _pilotPhoto.

Then, just as before, you check whether iCloud is enabled by probing the default store.

You fetch the default store in iCloudStore.

Finally, you get the PNG data out of the photo and store it in your key-value iCloud storage. You wrap up with a call to storage’s synchronize method.

Now every time the player takes a new photo it will upload to iCloud. Nice.

Fetching Data from iCloud

So far, you have the code in place to update the iCloud key-value storage with your local data. But how about fetching data from iCloud and updating your local storage?

Lucky for you iCloud shouts out whenever there are pending changes. You can simply observe the NSUbiquitousKeyValueStoreDidChangeExternallyNotification notification and update your local storage accordingly.

Since you want to start listening for iCloud changes as soon as RWGameData instance is created, the best way to do that is with a custom init.

You’re probably already super-familiar with this kind of code. You simply fetch the default store and you get the iCloud high score in cloudHighScore. In the end, you assign the highest score from the local and iCloud data to self.highScore.

High score is all set, so now onto the pilot’s photo. Add this code to the bottom of updateFromiCloud:

When unarchiving data from the game data file, you check for a stored photo, and if there is one you fetch the persisted NSData and use it to create a UIImage. You update RWGameData and save the image as the current pilot photo.

Do you know why you used the backing instance variable _pilotPhoto to set the photo instead of setting directly the property self.pilotPhoto? Try to figure it out. When you’re ready, click below to expand the answer.

[spoiler title=”Solution”]If you set the photo via the property you’ll:

Trigger the custom setter

Which will in turn upload the photo to iCloud

Which will in turn fire an iCloud update for your app

Which will trigger an iCloud notification

Which will fire up updateFromiCloud

Which will set the photo via self.pilotPhoto

Which will trigger the custom property setter

… etc. … etc. … etc. … FOREVER!
Long story short – you don’t want to use the custom setter in this case, changing the instance variable directly is the way to go.[/spoiler]

Now you’ve done all the necessary updates. To make sure all of the changes are persisted, also add a call to save the game data file to the disc.

[self save];

So you’ve stored data to iCloud and fetched with no (hopefully) problems. Don’t run the game just yet though.

Since iCloud is accessible via network, access and changes distribution are not instantaneous. Sometimes you’ll start the game and a few seconds into the gameplay the new photo will show up. Therefore, you need a way to update the game data and the scene changes that come in during gameplay.

You’ve already done half of the work, because you receive a notification when changes come in from iCloud. You just need to fire another notification when you’ve updated the game data so that MyScene knows to update the UI too.

Open RWGameData.h, and under the import statement define your notification’s name constant:

postNotificationName:object: will emit a notification through NSNotificationCenter. All you need to do is catch it in your scene class and update the on-screen high score and pilot photo.

Now open MyScene.m and scroll towards the bottom of initWithSize:. Find the [self setupHUD]; line. This is the right place to start observing for notifications about changes to the HUD. Add below that line:

Where To Go From Here?

You covered a lot in the second part of this tutorial by adding the following new features:

Keychain integration and basic security against cheaters

iCloud integration for your game

Key-value storage for the player’s high score and photo

Observing incoming changes from the cloud and updating the scene as they come in

Now you know a lot more about how to save your game data, but there’s always more to learn. Here are a few ideas to dive deeper into this subject:

You didn’t get to display the total distance flown by the user. You can add a new label for that to the scene.

How about keeping the last three (or more) scores for the player? This will give you an opportunity to use encodeObject:forKey: with an NSArray instance

Space Game Starter Kit

But there’s even more to the Space Shooter project than what you see here! Are you aware that Ray has developed the Space Shooter game into a full blown game starter kit? If you lay your hands on the starter kit, you get an epic, detailed tutorial, plus the code to a complete space game based on Space Shooter. Pretty cool, right?

Check out the Space Shooter starter kit – it comes with art, music, and source code; the kit extends the game by adding bad guys, power-ups, more weapons and level bosses.

If you have any questions or comments, please share in the comments below.