Working with Players in Game Center

Players are a critical part in any game that supports Game Center, because all Game Center features are related to the players. As a game developer, you need to understand some of the infrastructure that Game Center uses to support player accounts and how you implement it in your app. After reading this chapter, you will understand how to manage player information in your game. In particular, you’ll learn:

Game Center Manages Player Accounts

To take advantage of Game Center’s features, a user creates a Game Center account that identifies to identify themselves as a specific user. That user is known as a player on Game Center. The Game Center service keeps track of critical account information for that player, such as who that player is, what games that player has played, what the player has accomplished in each game, and who that player’s friends are. Some of this information is directly available to your game; typically this is information specifically related to the game and general information about the player.

When a player wants to access Game Center on a particular device, the player signs in, or authenticates on that device. A player authenticates their account by launching the Game Center app or by launching any game that implements Game Center support. In either case, the player is presented with an interface to provide their account name and password. Once authenticated, the player is associated with that device persistently until they explicitly sign out of Game Center in the Game Center app. Only one player is allowed to be authenticated on a device at a time; for a new player to be authenticated on the device, the existing authenticated player must sign out first.

Game Center is intended to be a social experience. Game Center allows a player to invite other players to be friends. When two players are friends, they can see each other’s status in the Game Center app, compare scores, and invite each other into matches more easily. Through Game Kit, your game can also access some information about a local player’s friends or allow the player to invite players to become friends. For example, you can use this functionality to allow a player to send a friend invitation to a player they just met in a match played within your game.

Player Identifier Strings Uniquely Identify Players

Every player account is uniquely identified by a player identifier string. The identifier string is created when the player’s account is first created and never changes, even if other information in the account changes. Thus, player identifiers are the only reliable way to track a particular player. For this reason, the Game Kit API uses player identifiers wherever a specific player needs to be identified. If Game Center needs to identify a specific player in your game, the Game Kit API returns that player’s identifier. Your game uses a player identifier to retrieve information from Game Center about that player.

In addition to using player identifiers in your interactions with Game Center, your game should also use the player identifier whenever it wants to store data locally about a specific player. For example, if your game stores data to track a player’s progress (such as on the device, on your own server, or on iCloud), use player identifiers to distinguish between multiple players playing on the same device. That way, if a different player signs into the device, you can immediately personalize the experience by showing content specific to that player.

The Local Player Is the Player Signed in to the Device

When you design your game, you’ll find that your game is often aware of multiple players at the same time. For example, if you design a game using multiplayer networking, then you have information—and a player identifier—for each player in the match. However, on any particular device, one player always takes precedence over other players. The local player is the player that is currently authenticated to play on that device. In Figure 3-1, two players are connected in a network match. On the left device, Bob is the local player and Mary is a remote player. On the right device, Mary is the local player and Bob is a remote player.

Figure 3-1  Local and remote players

Almost all classes in Game Kit that send data to or retrieve information from Game Center expect the device to have an authenticated local player. The work those classes do is always on behalf of the local player. For example, if your game reports scores to a leaderboard, it can only report a score earned by the local player. As such, before using any Game Center features, your game must first authenticate that there is a local player on the device. Game Kit returns an error to your game if it attempts to perform Game Center-related tasks that require an authenticated player when one isn’t available on the device.

Player Objects Provide Details About Players

When your game needs details for a particular player, it retrieves those details by making a query to Game Center using the player’s player identifier. Game Kit returns those details to your game in a GKPlayer object. Table 3-1 lists some of the more interesting properties on a player object.

Table 3-1  Important Player Object Properties

Property

Description

playerID

A string that holds the player identifier string used to retrieve this player information.

displayName

A user-readable string you can display for this player in your own user interface. For example, in a network match, you might show the display names for each player in the match so that everyone knows who they are playing against.

isFriend

A Boolean value that states whether the player is a friend of the local player. Note that this property reflects the general design of Game Center; all information returned to your game is based on the local player that is signed in to the device.

The GKLocalPlayer class is a special subclass of the GKPlayer class, and includes additional properties specific to the local player:

  • The friends property is populated with the identifier strings for other players on Game Center that are marked as the local player’s friends.

  • The underage property states whether this player is underage.

Table 3-2  Local Player Object Properties

Property

Description

friends

An array of player identifiers for the players who are friends of the local player. This property is populated only after your game specifically requests a list of identifiers for the local player’s friends from Game Center.

underage

A Boolean value that states whether the local player is underage. Some Game Center features are disabled when the value of this property is YES; Game Center returns a GKErrorUnderage error if you try to use those features. Your game can also use this property to decide whether it should disable some of its own features for an underage player.

In addition to the display name for a given player, if the player has provided a photo, you can download that player’s photo to your game and use it in your game’s user interface.

Common Tasks When Working with Players

Authenticating a Local Player on the Device

Your game should start to authenticate the player as early as possible after launching, even before you present the user interface. Authentication is an asynchronous process and won’t slow down the loading of your title screen. Waiting until after the title screen is presented before authenticating the user only increases the delay until the player can play your game. The main reason you need to authenticate the player as early as possible is so that iOS can launch your game specifically to handle events that the player is interested in. For example, if your game supports turn-based matches, it might be launched because the player has already tapped a notification banner. Thus, you need to authenticate early so that your game can retrieve and process the Game Center event.

When your game authenticates a player, Game Kit first checks to see whether there is already an authenticated player on the device. Because a player stays authenticated until they explicitly sign out of Game Center, it is quite common that an authenticated player is already on the device. In this situation, a banner is briefly shown to let the player know that authentication succeeded, and then your game is immediately notified.

If there is not currently an authenticated player on the device, then an authentication dialog needs to be displayed so that the player can sign in with an existing account or create a new Game Center account. This is important, because it means that supporting authentication also means transparently supporting account creation.

The actual process of displaying the user interface to the player depends on which mechanism you use to authenticate. There are two ways you can authenticate a player. The original mechanism first used in iOS 4.1 automatically displays a user interface to the player if necessary, but this may happen at an unusual time during your game’s launch sequence. The newer mechanism, first implemented in iOS 6 and OS X v10.8.2, hands your game a view controller to display at a time of your choosing. Typically, this means your game can cleanly pause its own animations and other features before displaying the view controller. The newer mechanism allows you to create a better user experience for the player, and thus is the preferred way to implement authentication.

Authenticating a Local Player (iOS 6, OS X v10.8.2)

Listing 3-1 shows how to set an authentication handler. The method retrieves the shared instance of the GKLocalPlayer class and then sets that object’s authenticateHandler property to point to a block object that handles authentication events. Once you set an authentication handler, Game Kit automatically authenticates the player asynchronously, calling your authentication handler as necessary to complete the process. Each time your game moves from the background to the foreground, Game Kit automatically authenticates the local player again.

Listing 3-1  Setting an authentication handler

- (void) authenticateLocalPlayer
{
    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
         if (viewController != nil)
         {
             //showAuthenticationDialogWhenReasonable: is an example method name. Create your own method that displays an authentication view when appropriate for your app.
             [self showAuthenticationDialogWhenReasonable: viewController];
         }
         else if (localPlayer.isAuthenticated)
         {
             //authenticatedPlayer: is an example method name. Create your own method that is called after the loacal player is authenticated.
             [self authenticatedPlayer: localPlayer];
         }
         else
         {
             [self disableGameCenter];
         }
     }];
}

The authentication handler shows the distinct conditions your code needs to handle:

  • If the device does not have an authenticated player, Game Kit passes a view controller to your authentication handler. When presented by your game, this view controller displays the authentication user interface. Soon after, your game should pause other activities that require user interaction and present this view controller. Game Kit dismisses this view controller automatically when complete, and calls your authentication handler again.

  • If the authentication process succeeded, the viewController parameter passed into your authentication handler holds nil. The local player object’s authenticated property holds YES and its other properties are set to match those of the connected player. Your game should complete the authentication process.

  • If the authentication process failed, the viewController parameter passed into your authentication handler holds nil. The local player object’s authenticated property holds NO and its other properties are cleared. Your game should disable all Game Center features.

Always check the authenticated property on the local player object to determine whether Game Kit was able to authenticate a local player. Do not rely on the error received by your game to determine whether an authenticated player is available on the device. Even when an error is returned to your game, Game Kit may have sufficient cached information to provide an authenticated player to your game. Also, it is not necessary for your game to display errors to the player when authentication fails; Game Kit already displays important errors to the player on your behalf. Most authentication errors are returned to your game primarily to assist you with debugging.

Authenticating a Local Player (iOS 5, OS X v10.8)

Listing 3-2 shows an implementation of a method that authenticates the local player. The method retrieves the shared instance of the GKLocalPlayer class and calls that object’s authenticateWithCompletionHandler: method. Authenticating the local player happens asynchronously; your game continues to launch after initiating the player authentication. If necessary, this method call displays the authentication user interface over your game. When authentication completes, Game Kit calls the block object provided by your game.

If your game supports multitasking, Game Kit keeps a reference to this completion handler until your app terminates and it cannot be changed. This means any objects referenced in the block are also kept in memory indefinitely. Each time your game moves from the background to the foreground, Game Kit automatically authenticates the local player again and calls your completion handler to provide updated information about the state of the authenticated player.

Listing 3-2  Authenticating the local player

- (void) authenticateLocalPlayer
{
    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    [localPlayer authenticateWithCompletionHandler:^(NSError *error) {
         if (localPlayer.isAuthenticated)
         {
             // Player was successfully authenticated.
             // Perform additional tasks for the authenticated player.
         }
     }];
}

Your game should always check the authenticated property on the local player object to determine whether Game Kit was able to authenticate the player. Do not rely on the error received by your game to determine whether an authenticated player is available on the device. Even when an error is returned to your game, Game Kit may have sufficient cached information to provide an authenticated player to your game. Also, it is not necessary for your game to display errors to the player when authentication fails; Game Kit already displays important errors to the player on your behalf. Most authentication errors are returned to your game primarily to assist you in debugging it.

Common Authentication Errors and Their Meanings

Two common errors that can be received by your game are worth describing in more detail:

  • Receiving a GKErrorGameUnrecognized error means that you have not enabled Game Center for your app in iTunes Connect. Sign in to your iTunes Connect account and verify that your app has Game Center enabled. Also, confirm that the bundle identifier in your Xcode project matches the bundle identifier you assigned to your app in iTunes Connect.

  • Receiving a GKErrorNotSupported error means that the device your game is running on does not support Game Center. You should disable all Game Center related features.

Enable Other Game Center Code Immediately After a Player Is Successfully Authenticated

Once the player has been successfully authenticated, your game can read other properties on the local player object to determine the player’s display name and other attributes. You can also use other classes that access Game Center. In most cases, your game should immediately enable other code related to Game Center. For example, here are some common tasks that most games perform after successfully authenticating the local player:

  • Read the displayName property to retrieve the local player’s name. Use this display name throughout your game when you want to refer to the player; do not prompt the player separately for their name.

  • Add event handlers to receive events for Game Center features. For example, turn-based matches, real-time matches and challenges all require event handlers to process Game Center events. Because your game may have been launched specifically to receive a pending invitation; adding event handlers immediately after authenticating the player allows those events to be processed promptly.

  • Retrieve the local player’s previous progress on achievements. See Listing 5-4.

  • Retrieve a list of player identifiers for the local player’s friends. This is a first step before loading more detailed information about those players. See “Retrieving the Local Player’s Friends.”

  • If your game stores its own custom information for a particular player (such as state variables indicating the player’s progress through your game), your completion handler might also load this data so that it can restore the player’s progress.

Authenticating the Local Player in a Multitasking App

A game that supports both multitasking and Game Center must take additional steps when authenticating the local player. When your game is in the background, the status of the authenticated player may change, because the player may sign out or a new player may sign in. As such, you can never rely on the information staying the same when your game moves into the background,.

Here are some guidelines for authenticating the local player in a game that supports multitasking:

  • Like other multitasking apps, your game should archive its state before moving into the background.

  • As soon as your game moves to the background, the value of the local player object’s authenticated property becomes and remains invalid until your game moves back to the foreground. You cannot read the value to determine if the player is still authenticated until Game Kit reauthenticates the player and calls your authentication handler. Your game must act as though there is not an authenticated player until your completion handler is called. Once your handler is called, value stored in the authenticated property is valid again.

  • If the value of the authenticated property changed to NO, then there is no longer a local player authorized to access Game Center content. Your game must dispose of any Game Kit objects that were created using the previous local player.

  • If the value of the authenticated property is YES, then there is a local player authenticated on the device. However, a new player may have logged in. Your game should store the player identifier for the local player after it authenticates the player. Then, on future calls to your completion handler, compare the value of the local player object’s playerID property to the identifier stored by your game. If the identifier changed, then a new player has signed in. Your game should dispose of any objects associated with the previous player and then create or load the appropriate state for the new local player.

Retrieving the Local Player’s Friends

You may want to your game to reference the local player’s friends. For example, you might do this if you are implementing your own custom matchmaking or challenge interface and want to allow the player to pick and choose players from the friends list. Unlike the other properties of a local player object, the friends property is not filled when the local player object is returned to your game because that data can potentially be quite large and take a while to send over the network. Instead, you choose when you want your game to load this information.

Retrieving details about the local player’s friends is a two-step process. First, your game loads the list of player identifiers for the local player’s friends (which also sets the friends property). Then, as with any other player identifiers, your game calls Game Center to retrieve the details for those players.

Listing 3-3 shows how your game loads the list of player identifiers for the local player’s friends. It then calls the loadPlayerData: method defined in Listing 3-5 to fetch the details for those players. When using iOS 6.0 and earlier, change the friendIDs property to friends.

Listing 3-3  Retrieving a local player’s friends

- (void) retrieveFriends
{
   GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
   if (lp.authenticated)
   {
      [lp loadFriendsWithCompletionHandler:^(NSArray *friendIDs, NSError *error) {
         if (friendIDs != nil)
         {
            [self loadPlayerData: friendIDs];
         }
      }];
   }
}

Listing 3-4  Retrieving a local player’s friends (previous)

- (void) retrieveFriends
{
   GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
   if (lp.authenticated)
   {
      [lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
         if (friends != nil)
         {
            [self loadPlayerData: friends];
         }
      }];
   }
}

Retrieving Information About Players

Regardless of which Game Kit class returned player identifiers to your game, you retrieve detailed information about those players in the same way by calling the loadPlayersForIdentifiers:withCompletionHandler: class method on the GKPlayer class. Listing 3-5 shows a skeletal implementation. This Game Kit method takes two parameters. The first is an array of player identifiers for the players you are interested in. The second is a completion handler to be called after Game Kit retrieves the data from Game Center. Game Kit loads the data asynchronously in the background and calls your completion handler after the task completes. The completion handler receives an array of GKPlayer objects (one for each identifier) as well as an error parameter.

Listing 3-5  Retrieving player details

- (void) loadPlayerData: (NSArray *) identifiers
{
    [GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
        if (error != nil)
        {
            // Handle the error.
        }
        if (players != nil)
        {
            // Process the array of GKPlayer objects.
        }
     }];
}

If Game Kit was unable to load information for all of the players, it provides an error to the completion handler. When this occurs, the players parameter may provide a partial array for the players that Game Kit was able to obtain information about. For this reason, Listing 3-5 tests the error condition separately from processing the array of player objects.

Loading a Photo for a Player

In addition to the information found in the player object, you can also attempt to load a photo for the player. Not all player objects have photos associated with them, so your code must be able to work without them.

Listing 3-6 shows a typical implementation of this concept, which follows the same pattern as other Game Kit classes. It calls the loadPhotoForSize:withCompletionHandler: method on the player object, and then waits for the completion handler to be called. Note that there is not a property on the player object that stores the returned photo. You must write your own code to associate player photos with player objects.

Listing 3-6  Loading a player’s photo

- (void) loadPlayerPhoto: (GKPlayer*) player
{
    [player loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler:^(UIImage *photo, NSError *error) {
        if (photo != nil)
        {
            [self storePhoto: photo forPlayer: player];
        }
        if (error != nil)
        {
            // Handle the error if necessary.
        }
     }];
}

Allowing the Local Player to Invite Other Players to Be Friends

The Game Center app allows players to send invitations to other players. Use the GKFriendRequestComposeViewController class to allow a player to send friend requests. The friend request must be presented modally by a view controller that your game creates and can not be pushed onto a navigation controller.

Listing 3-7 shows one way your view controller can allow a player to send a request to other players. For this method, an array of player identifiers is passed in as a parameter. The method instantiates a GKFriendRequestComposeViewController object, sets its delegate, and adds the list of players intended to receive the invitation. The view controller then presents the friend request and returns.

Listing 3-7  Displaying a friend request

- (void) inviteFriends: (NSArray*) identifiers
{
    GKFriendRequestComposeViewController *friendRequestViewController = [[GKFriendRequestComposeViewController alloc] init];
    friendRequestViewController.composeViewDelegate = self;
    if (identifiers)
    {
        [friendRequestViewController addRecipientsWithPlayerIDs: identifiers];
    }
    [self presentViewController: friendRequestViewController animated: YES completion:nil];
    [friendRequestViewController release];
}

When the player dismisses the friend request, the delegate’s friendRequestComposeViewControllerDidFinish: method is called. Listing 3-8 shows how your view controller should dismiss the request.

Listing 3-8  Responding when the player dismisses the friend request

- (void)friendRequestComposeViewControllerDidFinish:(GKFriendRequestComposeViewController *)viewController
{
    [self dismissViewControllerAnimated:YES completion:nil];
}