Chapter 9. Saving scores

Now that you are able to reach the end of the game, it’s time to save your scores on the device and brag about it by publishing the scores to GameCenter. In this chapter we cover saving score using the following:

  1. NSUserDefaults from UIKit
  2. Leaderboards from GameKit

NSUserDefaults is an old framework from back when NextStep was around, which is why the class is prefixed with NS. Even though it is old, it is a quick way to save small chunks of data.

GameCenter was introduced in iOS5 and offers a quick way to saves scores online on their servers so that the scores can be viewed through the Game Center application. Besides a leaderboard, Game Center can be used for turn based games and to record achievements in the game as well.

In this chapter we will create a ScoreManager class, so let’s get started.

Score Manager

In our game we would like to have a class that can perform certain tasks, such as saving the score which would do all of the heavy lifting for us without having to create an instance of it every time we wish to save. This class would act more like a container of helper methods that can be called from wherever and it does not contain any state. In Swift we can create a container class with these helper methods using the class keyword in front of the method definition. Using this concept we can define our ScoreManager class as such:

public class ScoreManager {
    class func saveScore(score: Int, forLevel level: Int) {
        ...
    }
    
    class func getScoreForLevel(level: Int) -> Int? {
        ...
    }
    
    class func getAllHighScores() -> String? {
        ...
    }
}

In the above code example available on github in our Saving Scores playground you can see how we have defined three methods using the class keyword. This is similar to the word static in Java where the method can be called on the class directly without having to create an instance of it. This is especially helpful when you want to make a library of helper methods available without having to pollute the global scope.

You can read the full source code of our Pencil Adventure game here on Github.

To first save our score we need to decide our data structure. For our game we have decided to score the highest score for each level so we can use a data structure such as a dictionary to map each level to its high score. Let’s see how to define our saveScore method using this data structure.

class func saveScore(score: Int, forLevel level: Int) {
    var leaderboard = NSUserDefaults.standardUserDefaults().objectForKey("LeaderBoard") as? NSDictionary ?? NSMutableDictionary()
    var leaderboardMDict = leaderboard.mutableCopy() as NSMutableDictionary

    if let highestScore = leaderboard[level] as? Int {
        leaderboardMDict.setValue(highestScore < score ? score : highestScore, forKey: "Level (level)")
    } else {
        leaderboardMDict.setValue(score, forKey: "Level (level)")
    }
    NSUserDefaults.standardUserDefaults().setObject(leaderboardMDict, forKey: "LeaderBoard")
    NSUserDefaults.standardUserDefaults().synchronize()
}

In our code sample we stored out levels to score dictionary data structure using the NSUserDefaults. NSUserDefaults is a class written in Objective-C and we have access to it through the wrapper API Apple has made available in Swift. NSUserDefaults can store primative values such as Int, String and Float, but it can also store an Array or Dictionary of these primatives. In our case we want to save a dictionary of Level represented as String to Score represented as Int. We can also store more complex objects such as a class object, as long as we define two methods which are initWithCoder and encodeWithCoder. The first method initWithCoder is called when you read data from NSUserDefaults and want to create an instance of a class using the data stored in NSUserDefaults. The encodeWithCoder is called when you want to write the instance object in a format that can be correctly read next time. In both methods you define the types of each attribute in that object so that the data can be read or written correctly to and from disk.

Here in the score example we want to store the scores as a Dictionary of primitive values, so we will not be defining encoders and decoders making our implementation simple. NSUserDefaults takes a key to store this data that is represented as string. So we will call our key “Leaderboard” and use this when retrieving the data later to view the high scores. The data stored using NSUserDefaults is specific only to the application, so we do not have to worry about conflicting with another application key.

The API we defined is simple, where we give it a score for a level and can call it as such:

ScoreManager.saveScore(10, forLevel: 1)

The implementation for this API is simple. First, get the dictionary object if available and if it is not then create a new NSMutableDictionary. Swift offers a new `??` operator that behaves similar to a ternary operator in JavaScript, but with much better readability. It basically says that if the value in the left hand side is not null or nil, then return the value on the right. This is good when you are not sure if the value exists and you can set a default value before assigning it to a variable. For example:

var leaderboard = NSUserDefaults.standardUserDefaults().objectForKey("LeaderBoard") as? NSMutableDictionary ?? NSMutableDictionary()

We then define few more methods such as getAllHighScores and getScoreForLevel, which can be used throughout the game. One thing to note is that these methods return Optional values so you need to put an exclamation mark after them to see the results extracted from the Optional wrapper.

ScoreManager.getAllHighScores()!

Adding Leaderboard

Saving scores locally is great but not as great as sharing with friends and the world. Let’s be competitive and create a leaderboard where you can view the highest scores for each level on Game Center.

Game Center provides you with a rich API that you can leverage to post your players score, and it will keep a tally of who has the highest score for the game that can be viewed by anyone from the Game Center application. This does require you to configure the application to use Game Center, so let’s get started.

Make sure that the application is Game Center enabled in its capabilities. Turn the switch on for Game Center and it will add the GameKit framework to the project and entitle our application to post data to Game Center.

Before continuing you must do several things:

  • Create an App on iTunes Connect
  • Create a leaderboard for each level
  • Create a tester account

For these items, head over to iTunes Connect by going to https://itunesconnect.apple.com. Login follow the instruction on how to create an application in Xcode Game Publishing chapter. Once you have the application created you will see a Game Center tab. Click on that and you will go to the Game Center page for this application. Make sure the switch on this page says Enabled.

This is the page where you can add leaderboards. When adding a leaderboard you can do a single leaderboard or a combined, which is an aggregate of multiple scores.

For our game we will create four leaderboards for four different levels:

Now let’s create a test account. By default you cannot use your own Apple ID when testing. Also, testing needs to be done in the Sandbox mode, otherwise the score will not be recorded when running from the emulator, or using the development version of the application on the iPhone or iPad device. To create a tester account head over to Users and Roles in iTunes Connect and then click on Sandbox Testers and create a sandbox test user using a fake email and name.

Great, now you are set to write some code. After all of the configuring you must be wondering how to report scores to Game Center. It is actually quite easy.

You first need to reach your games local player that is currently the player so you can authenticate that player with the Game Center. There are three cases that we need to handle in our game when trying to post a score. They are:

  • Player has not logged into Game Center at all and needs to be authenticated
  • Player has logged into Game Center and has been authenticated
  • Player has logged into Game Center and cannot be authenticated due to password change or other reasons

To handle all of these cases we first need to give the local player a callback function that triggers authenticating the user and also is the function that gets called when the user is logged out, authenticated or not authenticated.

Below is the code needed to authenticate the user and then post the score for the appropriate level after the player is authenticated. As you can see we set authenticateHandler to a closure function that will present the GameCenter login form if the user is not authenticated. If, however, the user is authenticated then it will not pass a viewController to present and you can check if the players authenticated property is set to true or false. If it is true, use is authenticated and we can post the score. If not then we can show an error dialog or ignore it.

var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController : UIViewController!, error : NSError!) -> Void in
    if viewController != .None {
        let appdelegate = UIApplication.sharedApplication().delegate as AppDelegate
        appdelegate.window?.rootViewController?.presentViewController(viewController, animated: true, completion: nil)
    } else {
        if localPlayer.authenticated {
            var scoreToReport = GKScore(leaderboardIdentifier: "Leaderboard(level)", player: localPlayer)
            scoreToReport.value = Int64(score)
            GKScore.reportScores([scoreToReport], withCompletionHandler: nil)
        } else {
            // Player could not authenicated
        }
    }
}

Add this code snippet to the saveScore function in ScoreManager where you not only store the score locally, but also on Game Center. To check how the score report looks we can go to the Game Center application and tap on the game and view the leaderboard and the high scores.

Summary

Using both the UIKit and GameKit we are able to easily store the game score locally on the device and on Game Center. Now you can have players compete with each other bragging about their high scores and we have now gamified your game.

GameKit made it easy for us to post our scores online on Game Center. Likewise, UIKit provided us with NSUserDefaults to save the score on the device. A good next challenge will be to add Achievements to this game and have them be published to Game Center.

In the next chapter we will use the same principle of create a helper class to play sounds in our game.

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

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