Networking and Social Media - iOS Game Development Cookbook (2014)

iOS Game Development Cookbook (2014)

Chapter 12. Networking and Social Media

Nobody games alone. At least, not anymore.

While single-player games remain one of the most interesting forms of interactive media, it’s increasingly rare to find a game that never communicates with the outside world. Angry Birds, for example, is by all definitions a single-player game; however, with only a couple of taps, you can send your most recent score to Twitter and Facebook. We’ll be looking at how you can implement this functionality inside your own game.

In addition to this relatively low-impact form of engaging other people with the player’s game, you can also have multiple people share the same game through networking. This is a complex topic, so we’re going to spend quite a bit of time looking at the various ways you can achieve it, as well as ways in which it can get challenging and how to address them.

We’ll also be looking at Game Center, the built-in social network that Apple provides. Game Center allows you to let users share high scores and achievements, as well as challenge their friends and play turn-based games. Before we get to the recipes, we’ll take a quick look at it to familiarize you with how it works.

Using Game Center

Many of the powerful networking capabilities exposed to your app come from Game Center, Apple’s game-based social network. Game Center handles a number of capabilities that you often want to have as a game developer, but that it would be very tedious to develop yourself. These include:

§ Player profiles

§ High score tables

§ Match-making

§ Turn-based gameplay

You get all of these for free when you add Game Center to your game, and all you need to do is write code that hooks into the service. To get started, you first need to turn on Game Center support for your app in Xcode, and sign your player into Game Center when the game starts.

Turning on Game Center support is easy. First, you’ll need to have a working iOS Developer account, since Game Center relies on it. Then:

1. In Xcode, select your project in the Project Navigator.

2. Open the Capabilities tab.

3. Open the Game Center section, and turn the switch to On (see Figure 12-1).

Enabling Game Center.

Figure 12-1. Enabling Game Center

Xcode will then walk you through the process of configuring your app to have access to Game Center.

When a player wants to use Game Center, that player needs to have a profile. This means that when you want to develop a game that uses Game Center, you also need to have a profile. When an app is running on the iOS simulator, or is running on a device and is signed with a Developer certificate, it doesn’t get access to the “real” Game Center service. Instead, it gets access to the sandbox version of the Game Center service, which is functionally identical to but shares no data with the real one. This means that you can play around with your game without the risk of it appearing in public. It also means that you need to get a profile on the sandbox version of the Game Center service.

Here’s how to get a profile on the sandbox. You’ll first need to create a new, empty Apple ID by going to http://appleid.apple.com. Then:

1. Open the iOS simulator.

2. Open the Game Center application (Figure 12-2).

The Game center application in the iOS Simulator

Figure 12-2. The Game center application in the iOS Simulator

3. Sign in to your new Apple ID. You’ll be prompted to set up the new account.

WARNING

Don’t sign in to your existing Apple ID. Existing Apple IDs are likely to have already been activated in the real, public Game Center, which means they won’t work in the sandbox, and you’ll get an error saying that your game is not recognized by Game Center.

Once this is done, a player profile is created in the sandbox, which you can use in your game.

To actually use Game Center, your game needs to ask Game Center if it has permission to access the player’s profile. You do this by authenticating the player, which you do by first getting the local player, and providing it with an authentication handler:

- (void) authenticatePlayer {

GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];

localPlayer.authenticateHandler = ^(UIViewController* viewController,

NSError* error) {

if (viewController != nil) {

[self presentViewController:viewController animated:YES

completion:nil];

} else if (localPlayer.isAuthenticated) {

// We're now authenticated!

} else {

// Game Center is unavailable, either due to the network or because

// the player has turned it off.

}

};

}

Here’s how this works. When you provide the GKLocalPlayer object with an authentication handler, Game Center immediately tries to authenticate the player so that the game can get access to the Game Center features. Whether this succeeds or not, the authentication handler is called.

The handler receives two parameters: a view controller, and an error. If Game Center needs the user to provide additional information in order to complete the authentication, your app needs to present the view controller and let the user provide whatever information Game Center needs. If the view controller parameter is nil, you check to see if the GKLocalPlayer has been authenticated. If it has, great! If not, you can find out why by looking at the provided NSError parameter.

NOTE

There are many reasons why the player may not get authenticated. These include network conditions, the player not having a Game Center account, the player declining to sign up for one, the player being underage, or parental restrictions in place.

Regardless of the reason, your game needs to be designed to handle not being able to access Game Center. If your game displays errors or refuses to work without the user signing in, you’ll have trouble getting it approved by Apple when you submit it to the store.

Getting Information About the Logged-in Player

Problem

You want to find out information about the currently logged-in player, such as the user’s nickname and avatar.

Solution

Use the GKLocalPlayer class to get information about the player:

GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];

self.playerName.text = localPlayer.alias;

[[GKLocalPlayer localPlayer] loadPhotoForSize:GKPhotoSizeNormal

withCompletionHandler:^(UIImage *photo, NSError *error) {

self.playerPhotoView.image = photo;

}];

Discussion

Showing information about the player is a good way to indicate that she’s a part of the game. You can get the display name and player ID from a GKPlayer object. GKLocalPlayer is a special object that represents the currently signed-in user.

If you want to load the player’s photo, you have to do a special call. Note that not all users have a photo.

Getting Information About Other Players

Problem

You want to get information about the people that the player is friends with, such as their names and avatars.

Solution

Use the GKLocalPlayer class’s loadFriendsWithCompletionHandler: method to get the list of identifiers for each friend the player has. For each identifier you receive, use the loadPlayersForIdentifiers:withCompletionHandler: method to load information about that friend:

[[GKLocalPlayer localPlayer] loadFriendsWithCompletionHandler:^

(NSArray *friendIDs, NSError *error) {

[GKPlayer loadPlayersForIdentifiers:friendIDs withCompletionHandler:^

(NSArray *players, NSError *error) {

// Get info for each player

for (GKPlayer* player in players) {

NSLog(@"Friend: %@", player.displayName);

}

}];

}];

Discussion

Using the loadFriendsWithCompletionHandler: method, you get the list of players that the local player is friends with. Note that this method just gives you an array of NSStrings, which are the player IDs of the local player’s friends. To get more detailed information on each player, you call loadPlayersForIdentifiers:withCompletionHandler: and pass it the array of player ID strings.

Making Leaderboards and Challenges with Game Center

Problem

You want to show high scores and high-ranking players, and let players challenge their friends to beat their scores.

Solution

Go to iTunes Connect and register a new app. (You’ll need to provide information for a bunch of things needed for the App Store, including screenshots, but because Apple won’t actually look at any of this until you submit the game to the App Store, you can just supply placeholder text and blank images.)

NOTE

This recipe assumes you’re creating a new game that you want to have this functionality. However, you can easily adapt it to add a leaderboard to an existing game.

Once it’s all set up, click Manage Game Center (Figure 12-3); the Manage Game Center button is on the right, at the top of the list.

iTunes Connect

Figure 12-3. iTunes Connect

You’ll be asked if you want to set up the game as a standalone game, or as a group of games. If you choose a group of games, you’ll be able to share the leaderboard with other games that you make. If you’re just making a single game, choose Single Game, as in Figure 12-4.

iTunes connect will ask you whether you want to set up a single game or multiple games

Figure 12-4. iTunes connect will ask you whether you want to set up a single game or multiple games

Next, click Add Leaderboard. You’ll be prompted to provide information about the leaderboard, including:

§ The name of the leaderboard (shown only to you, inside iTunes Connect, and not shown to the player)

§ The ID of the leaderboard, used by your code

§ How to format the leaderboard scores (whether to show them as integers, decimal numbers, currency, or times)

§ Whether to sort them by best score or by most recent score

§ Whether to sort them with low scores first or high scores first

§ Optionally, a specified range that scores can be within (Game Center will ignore any scores outside this range)

You then need to provide at least one language that the leaderboard will be shown in. To do this, click Add Language in the Leaderboard Localization section, and choose a language (see Figure 12-5). For each language you provide, you need to provide information on how your scores are being described (see Figure 12-6).

Adding a language to your leaderboard.

Figure 12-5. Adding a language to leaderboard

The information you need to provide for each language.

Figure 12-6. The information you need to provide for each language

To submit a high score, you need to create a GKScore object and provide it with the score information. When it’s created, it’s automatically filled out with other important information, such as the player ID of the player who’s reporting the score, the date that the score was created, and other data.

When you create the score, you need to provide the ID of the leaderboard. This is the same ID as the one that you gave to iTunes Connect when creating the leaderboard. This tells Game Center where the score should end up.

Once that’s done, you report the score to Game Center using the reportScores:withCompletionHandler: method. This method takes an NSArray of GKScore objects, which means you can submit multiple scores at the same time:

GKScore* score =

[[GKScore alloc] initWithLeaderboardIdentifier:@"leaderboardtest1"];

score.value = scoreValue;

[GKScore reportScores:@[score] withCompletionHandler:^(NSError *error) {

NSLog(@"Completed; error = %@", error);

}];

To get the scores, you use the GKLeaderboard class. This class lets you get the list of leaderboards that have been defined in iTunes Connect, and from there, get the list of scores in the leaderboard:

// Get the list of scores defined in iTunes Connect

[GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards,

NSError *error) {

// For each leaderboard:

for (GKLeaderboard* leaderboard in leaderboards) {

// Get the scores

[leaderboard loadScoresWithCompletionHandler:^(NSArray *scores,

NSError *error) {

NSLog(@"Leaderboard %@:", leaderboard.title);

// Show the score

for (GKScore* score in scores) {

NSLog(@"%@", score.formattedValue);

}

}];

}

}];

When you have a GKScore, you can issue a challenge to one or more of the player’s friends. They’ll receive a challenge notification, which will prompt them to try and beat the challenge. If a friend submits a score to the leaderboard that’s better than this challenge, your player will receive a notification saying that the challenge has been beaten.

You issue a challenge by presenting a view controller to the players with the specified player IDs (listed in an NSArray):

NSArray* friendIDs = ... // an NSArray containing the player IDs you want

// to challenge

UIViewController* challengeCompose =

[score challengeComposeControllerWithPlayers:friendIDs

message:@"Beat this!" completionHandler:^(UIViewController *composeController,

BOOL didIssueChallenge,

NSArray *sentPlayerIDs) {

[self dismissViewControllerAnimated:YES completion:nil];

}];

[self presentViewController:challengeCompose animated:YES completion:nil];

Discussion

Leaderboards and challenges are a great way to encourage the player to try to best other players, and to keep track of their progress in your game. There’s a huge degree of flexibility available to you when you create them.

If you want to quickly show a user interface that displays the scores, you can use the GKGameCenterViewController class. This is a view controller that shows Game Center information for the current player, including that player’s scores and challenges.

To use it, you first need to make your view controller conform to the GKGameCenterControllerDelegate protocol. Then, when you want to show the view controller, you create it and present it to the player:

GKGameCenterViewController* viewController =

[[GKGameCenterViewController alloc] init];

viewController.gameCenterDelegate = self;

[self presentViewController:viewController animated:YES completion:nil];

You then implement the gameCenterViewControllerDidFinish: method, which is called when the player decides to close the Game Center view controller. All this method has to do is dismiss the view controller:

- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)vc {

[self dismissViewControllerAnimated:YES completion:nil];

}

Finding People to Play with Using Game Center

Problem

You want your players to find people to play games with over the Internet.

Solution

To find people to play with, you first create a GKMatchRequest:

GKMatchRequest* request = [[GKMatchRequest alloc] init];

request.maxPlayers = 2;

request.minPlayers = 2;

You then create a GKMatchmakerViewController and give it the GKMatchRequest:

GKMatchmakerViewController* viewController = [[GKMatchmakerViewController alloc]

initWithMatchRequest:request];

viewController.matchmakerDelegate = self;

[self presentViewController:viewController animated:YES completion:nil];

You also need to conform to the GKMatchmakerViewControllerDelegate protocol, and implement three important methods:

- (void)matchmakerViewController:(GKMatchmakerViewController *)vc

didFindMatch:(GKMatch *)match {

// Start using the match

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)matchmakerViewController:(GKMatchmakerViewController *)vc

didFailWithError:(NSError *)error {

// We couldn't create a match

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)vc {

// The user cancelled match-making

[self dismissViewControllerAnimated:YES completion:nil];

}

The really important one is matchmakerViewController:didFindMatch:. This gives you a GKMatch object, which is what you use to communicate with other players.

NOTE

You’ll note that in all three cases, it’s up to you to dismiss the view controller.

Discussion

GKMatchRequest objects let you define what kinds of players you’re looking for in a match. If you set the playerGroup property on a GKMatchRequest to any value other than 0, the player will only be matched with other people who have the same value for playerGroup.

Matches can be peer-to-peer or hosted; peer-to-peer means that players pass information between themselves directly, while hosted games go through a server that you run.

Many games work best in a client/server model, in which one player (the server) acts as the owner of the game, and all clients communicate with it. This simplifies the problem of keeping the clients in sync, since each client just needs to stay in sync with the server. However, it puts additional load on the server; this has to communicate with every client, which means that the server has to be the player with the best ability to perform these additional duties.

You can configure Game Center to automatically select the best player to act as the server, using the chooseBestHostPlayerWithCompletionHandler: method. This method runs checks on all players to find out which one has the best connection, and then calls a block and passes it the player ID of the player who should be host (or nil if the best host couldn’t be found). If the player ID returned is your player ID, you’re the server, and you should start sending updates to other player, and receiving input from them. If the player ID is not your player ID, you’re the client, and you should start sending commands to the server and receiving game updates. If the player ID is nil, there was a problem, and each copy of the game running on the different players’ devices should let its player know that network conditions probably aren’t good enough to run a game.

Using Bluetooth to Detect Nearby Game Players with the Multipeer Connectivity Framework

Problem

You want to let players play with people who are physically close by, using Bluetooth.

Solution

Use the Multipeer Connectivity framework. First, add the framework to your class, and import the header file:

#import <MultipeerConnectivity/MultipeerConnectivity.h>

Multipeer connectivity works like this:

First, you make your class conform to the MCSessionDelegate protocol. You need to implement several methods as part of this protocol (many of which aren’t used, but you’ll get a compiler warning if they’re not there):

+

- (void)session:(MCSession *)session

peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {

// A peer has changed state—it's now either connecting, connected,

// or disconnected

}

- (void)session:(MCSession *)session

didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {

// Data has been received from a peer

}

- (void)session:(MCSession *)session

didStartReceivingResourceWithName:(NSString *)resourceName

fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress {

// A file started being sent from a peer (not used in this example)

}

- (void)session:(MCSession *)session

didFinishReceivingResourceWithName:(NSString *)resourceName

fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL

withError:(NSError *)error {

// A file finished being sent from a peer (not used in this example)

}

- (void)session:(MCSession *)session

didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName

fromPeer:(MCPeerID *)peerID {

// Data started being streamed from a peer (not used in this example)

}

Next, you create a peer ID. A peer ID is an object of type MCPeerID, and you create it by providing a display name. This should be the local player’s alias, which means you should create it after the player has authenticated with Game Center:

MCPeerID* peerID = [[MCPeerID alloc] initWithDisplayName:

[GKLocalPlayer localPlayer].alias];

When you’ve created the MCPeerID, keep it around in an instance variable.

NOTE

You don’t have to use Game Center. If you prefer, you can just create an MCPeerID using a string of your choosing—consider letting the user enter his name. The advantage of using Game Center is that you save your player some typing.

Once you have the MCPeerID, you create an MCSession and provide it with the peer ID:

MCSession* session = [[MCSession alloc] initWithPeer:peerID];

session.delegate = self;

Once you have the MCSession, you can send data to it and receive data from it. What you do next depends on whether you want to host a game or join someone else’s game. If you want to host a game and accept connections from others, you create an MCAdvertiserAssistant, tell it what kind of service you want to advertise, and start it up:

NSString* serviceType = @"mygame-v1"; // should be unique

MCAdvertiserAssistant* advertiser =

[[MCAdvertiserAssistant alloc] initWithServiceType:serviceType

discoveryInfo:nil session:session];

[self.advertiser start];

If you want to connect to someone else, you can create an MCBrowserViewController, give it your MCSession, and present it. To use this, your class needs to conform to the MCBrowserViewControllerDelegate protocol:

MCBrowserViewController* viewController =

[[MCBrowserViewController alloc] initWithServiceType:serviceType

session:self.session];

viewController.minimumNumberOfPeers = 2;

viewController.maximumNumberOfPeers = 2;

viewController.delegate = self;

[self presentViewController:viewController animated:YES completion:nil];

You then implement the browser delegate methods:

- (void)browserViewControllerDidFinish:(MCBrowserViewController *)vc {

// The MCSession is now ready to use

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)browserViewControllerWasCancelled:(MCBrowserViewController *)vc {

// The user cancelled

[self dismissViewControllerAnimated:YES completion:nil];

}

When a user connects to another device, the host calls the session:peer:didChangeState: method, indicating that a peer has joined.

Discussion

Multipeer connectivity doesn’t require any WiFi or cellular access, but only works with users who are close together—for example, in the same room. This means that you can’t connect to peers over the Internet in addition to nearby people—you’re either relying on WiFi or cellular networks for all communication, or not using them at all.

Making Real-Time Gameplay Work with Game Kit and the Multipeer Connectivity Framework

Problem

You want to send information to other players in the game in realtime.

Solution

The specific methods you use to send and receive data from other players depend on whether you’re using Game Center or Multipeer Connectivity.

If you’re using Game Center, you first need to have a GKMatch (see Finding People to Play with Using Game Center). Once you have a GKMatch, you can send data to your fellow players by using the sendData:toPlayers:withDataMode:error: method:

NSData* data = ... // some data you want to send to other players

GKMatch* match = ... // a GKMatch

NSArray* players = ... // an NSArray of player ID strings

NSError* error = nil;

[match sendData:data toPlayers:players withDataMode:GKMatchSendDataReliable

error:&error];

The sendData:toPlayers:withDataMode:error: method takes the data you want to send, an NSArray of player IDs that you want to send the data to, information on whether you want to send the data reliably or unreliably, and a pointer to an NSError variable. If, for some reason, sending the data fails, the method will return NO and the error will be set to an NSError object.

If you’re using Multipeer Connectivity, you send your MCSession object (see Using Bluetooth to Detect Nearby Game Players with the Multipeer Connectivity Framework) to the sendData:toPeers:withDataMode:error: method. It works in much the same way:

NSData* data = ... // some data you want to send to other players

MCSession* session = ... // an MCSession

NSArray* peers = ... // an NSArray of MCPeerIDs

NSError* error = nil;

[session sendData:data toPeers:players withDataMode:GKMatchSendDataReliable

error:&error];

You receive data in a similar way. Both MCSession and GKMatch will send their delegates NSData objects when data arrives.

To receive information from a GKMatch, you conform to the GKMatchDelegate protocol and implement the match:didReceiveData:fromPlayer: method:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data

fromPlayer:(NSString *)playerID {

// Do something with the received data, on the main thread

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

// Process the data

}];

}

The process is similar for receiving data from an MCSession:

- (void)session:(MCSession *)session didReceiveData:(NSData *)data

fromPeer:(MCPeerID *)peerID {

// Data has been received from a peer

// Do something with the received data, on the main thread

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

// Process the data

}];

}

NOTE

Data can come in from the network at any time, and your code could be in the middle of something important when it does. Because of this, you should always use operation queues to schedule the work of processing the received data on the main queue (see Working with Operation Queues).

Discussion

When you send data, you choose whether it should be sent reliably or unreliably. If you send the data reliably, the system guarantees that the data will arrive, and that multiple packets of data will arrive in the same order as they were sent. If a packet can’t be delivered, it keeps trying to resend it until it manages to go through. (If it still can’t get through, the entire connection will drop.) If you send it unreliably, then the system makes no promises as to whether the data will be received, or in which order it will be received. As compensation, sending data unreliably is much faster than sending data reliably.

There are a number of things you can do to ensure that your players enjoy a great experience:

§ Limit packet size to 2 KB. The bigger the packet is, the more risk there is that it will be broken into smaller packets, which can hamper performance.

§ Don’t send too often. Even real-time games probably only need to send data 10 to 15 times a second.

§ Don’t send too much. Only send updates on objects that have changed state since the last time any updates were sent over the network.

§ Don’t use reliable sending for data that will be redundant later. Using reliable mode consumes more resources, and it can slow down your game if used too much. If you’re updating the position of an object that’s moving often, use unreliable mode.

Creating, Destroying, and Synchronizing Objects on the Network

Problem

You want to create and remove objects in the game, and ensure that everyone sees when an object changes state.

Solution

To synchronize objects across the network, you need to be able to get information from them that represents what their current state is. The “current state” of an object is all of the important information that defines its situation: its position, its health, where it’s facing, and more. Because all games are different, the specific information that makes up game state in your game may vary.

In any case, you need to get the information from the object wrapped in an NSData object. For example, if you have an object whose entire state is a vector defining its position, you can create an NSData object for it like this:

unsigned int objectID = ... // some number that uniquely identifies this object

// in the game

GLKVector3 position = ... // the object's position

struct {

NSUInteger objectID;

GLKVector3 position;

} data;

data.objectID = self.objectID;

data.position = self.position;

NSData* dataToSend = [NSData dataWithBytes:&data length:sizeof(data)];

You can then send this data over the network. When it’s received, you then get the data, find the object that it’s referring to, and apply the change. If it’s an object that doesn’t exist yet, you create it:

NSData* dataReceived = ... // data received over the network

struct {

NSUInteger objectID;

GLKVector3 position;

} data;

[dataReceived getBytes:&data length:sizeof(data)];

GameObject* object = [self findObjectByID:data.objectID];

if (object == nil) {

object = ... // create the object

}

object.position = data.position;

Removing an object is similar, but instead of sending an NSData object that contains information about the object, you instead send a packet that instructs clients to remove the object from the game.

Only the server should be in charge of creating, updating, and removing objects. Clients should send instructions to the server, telling them what changes they’d like to make, but it’s up to the server to actually make those changes and send back the results of the changes to clients. This prevents clients from falling out of sync with each other, since the server is continuously telling them the “official” version of the game state. (For more on the client/server model of gameplay, see Finding People to Play with Using Game Center.

Discussion

It’s important that all players in a game have the same approximate game state at all times. If this ever stops being the case, the players aren’t able to interact in any meaningful way, since what they’re all seeing will be different.

When you send data that contains instructions to create or remove objects, you want it to be reliable because it’s important that all players have the same objects in their game. Updates can usually be sent as unreliably (see Making Real-Time Gameplay Work with Game Kit and the Multipeer Connectivity Framework).

If you’re using a component based model, you’ll need all components to be able to provide information to be sent over the network, so that every aspect of your object can be represented.

Don’t forget that not every detail of an object needs to be exactly replicated on all players. Important information, like the position of a monster, is important, while the position of sparks coming off it is not. If your game can work without that information being synchronized over the network, then don’t synchronize it.

Interpolating Object State

Problem

You want to minimize the amount of network traffic in your game, but ensure that everyone has a smooth experience.

Solution

When a packet is received from the server, note the time when it arrived, and subtract that from the time the last packet arrived. Store this value somewhere:

NSTimeInterval timeOfSecondMostRecentPacket;

NSTimeInterval timeOfMostRecentPacket = [NSDate timeIntervalSinceReferenceDate];

float smoothingTime = timeOfMostRecentPacket - timeOfSecondMostRecentPacket;

timeOfSecondMostRecentPacket = timeOfMostRecentPacket;

When a data packet arrives that updates the position of an object, first note the current location of the object (i.e., its position before applying the update). Then, over the course of smoothingTime, move the object from its original position to that in the most recently received update:

// When a new packet comes in:

float interpolationTime = 0.0;

// For each frame:

float deltaTime = ... // the time since the last frame

interpolationTime += deltaTime;

float interpolationPoint = interpolationTime / smoothingTime;

// 0 = start of interpolation, 1 = end of interpolation

GLKVector3 position = GLKVector3Interpolate(originalPosition,

destinationPosition,

interpolationPoint);

If an update packet arrives during this process (which is possible), restart the process, using the object’s current position and the new position.

Discussion

To save bandwidth, your server should send at a rate much lower than the screen refresh rate. While the screen is likely updating at 60 frames per second (at least, it should be!), the network should only be sending updatings 10 to 15 times per second.

However, if you’re only sending position updates at a rate of 10 frames per second, your network game will appear jerky, because objects will be moving around the screen at a low rate of updates.

What’s better is interpolation: when an update comes in, smoothly move the object from its current location to the location that you just received. This has the effect of always making the player be a few milliseconds behind the server, but makes the whole gameplay experience feel better.

Handling When a Player Disconnects and Rejoins

Problem

You want to gracefully handle when players drop out of the game, and when they rejoin.

Solution

If you’re using GKMatch, when a player’s connection state changes the delegate provided to your GKMatch receives the match:player:didChangeState: message:

- (void)match:(GKMatch *)match player:(NSString *)playerID

didChangeState:(GKPlayerConnectionState)state {

if (state == GKPlayerStateDisconnected) {

// Let the player know that the connection has been dropped

}

}

If a player drops from a GKMatch, the delegate gets sent the match:shouldReinvitePlayer: message. If this returns YES, Game Center will try to reconnect the player so that player rejoins the match:

- (BOOL)match:(GKMatch *)match shouldReinvitePlayer:(NSString *)playerID {

// Attempt to reconnect; the player will rejoin the match

return YES;

}

Note that this happens only if there are exactly two players in the match. If there are more, then the player’s gone forever, and you’ll have to tell the player to create a new match.

Discussion

If you’re using MCSession to do local, Bluetooth-based networking, there’s unfortunately no ability to reinvite players to join the game.

Players can leave the game in one of two ways: they can deliberately decide to leave the game, or they can drop out due to a crash, networking conditions, or something else outside their control. Losing a player is generally annoying to other players, but it can be even more annoying if your game doesn’t indicate the distinction between a player losing her connection and a player quitting the game. If your game lets your player know the difference, it feels nicer.

Making your game send an “I’m going away” packet immediately before quitting allows other players to understand the difference between a conscious decision to leave the game and a dropout.

Making Turn-Based Gameplay Work with GameKit

Problem

You want to use Game Center to coordinate turn-based gameplay.

Solution

First, generate a match request by creating a GKMatchRequest. You create the GKMatchRequest in the exact same way as if you were making a non-turn-based game, and you can learn more about how you do that in Finding People to Play with Using Game Center.

Next, you should make your class conform to the GKTurnBasedMatchmakerViewControllerDelegate protocol, which contains some necessary methods used for determining which turn-based match the player wants to play.

Once you have your GKMatchRequest, you create a view controller, GKTurnBasedMatchmakerViewController, and provide your match request to it:

GKMatchRequest* matchRequest = [[GKMatchRequest alloc] init];

matchRequest.minPlayers = 2;

matchRequest.maxPlayers = 2;

GKTurnBasedMatchmakerViewController* matchmaker =

[[GKTurnBasedMatchmakerViewController alloc] initWithMatchRequest:matchRequest];

matchmaker.turnBasedMatchmakerDelegate = self;

[self presentViewController:matchmaker animated:YES completion:nil];

Next, you need to implement methods from the GKTurnBasedMatchmakerViewControllerDelegate protocol:

- (void)turnBasedMatchmakerViewController:

(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:

(GKTurnBasedMatch *)match {

// Close the matchmaker

[self dismissViewControllerAnimated:YES completion:nil];

// Do something with the match. For example:

if ([match.currentParticipant.playerID

isEqual:[GKLocalPlayer localPlayer].playerID]) {

// We're the current player, so we can do something

} else {

// It's not our turn, so just show the game state

}

}

- (void)turnBasedMatchmakerViewControllerWasCancelled:

(GKTurnBasedMatchmakerViewController *)viewController {

// The matchmaker was closed without selecting a match

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)turnBasedMatchmakerViewController:

(GKTurnBasedMatchmakerViewController *)viewController

didFailWithError:(NSError *)error {

// The matchmaker failed to find a match for some reason

[self dismissViewControllerAnimated:YES completion:nil];

}

- (void)turnBasedMatchmakerViewController:

(GKTurnBasedMatchmakerViewController *)viewController

playerQuitForMatch:(GKTurnBasedMatch *)match {

// Tell the matchmaker view controller that we want to quit

// a game in which they're the current player.

[self dismissViewControllerAnimated:YES completion:nil];

NSData* matchData = match.matchData;

// Do something with the match data to reflect the fact that we're

// quitting (e.g., give all of our buildings to someone else,

// or remove them from the game)

// Tell Game Center that we've quit

[match participantQuitInTurnWithOutcome:GKTurnBasedMatchOutcomeQuit

nextParticipants:nil turnTimeout:2000.0 matchData:matchData

completionHandler:^(NSError *error) {

// Finished telling Game Center that we quit

}];

}

You should also conform to the GKLocalPlayerListener protcol, and implement methods that tell your code about when other players have changed the game state:

- (void)player:(GKPlayer *)player

receivedTurnEventForMatch:(GKTurnBasedMatch *)match

didBecomeActive:(BOOL)didBecomeActive {

// The game was updated. The player might have just become the current

// player in the game.

}

- (void)player:(GKPlayer *)player matchEnded:(GKTurnBasedMatch *)match {

// Tell the player that the match is over

}

Finally, you can update the game state, if it’s the current player’s turn. To end the turn, you do this:

GKTurnBasedMatch* match = ... // the current match

NSData* matchData = ... // the current game state

NSArray* nextParticipants = ... // an NSArray

// nextParticipants is an NSArray of GKTurnBasedParticipants. You can get the

// list of participants using match.participants. Game Center will tell the

// first participant in the array that it's their turn; if they don't do it

// within 600 seconds (10 minutes), it will be the player after that's turn,

// and so on. (If the last participant in the array doesn't complete their

// turn within 10 minutes, it remains their turn.)

[match endTurnWithNextParticipants:nextParticipants turnTimeout:600

matchData:matchData completionHandler:^(NSError *error) {

// We're done sending the match data

}];

You can also end the entire match by doing this:

// End the match. All players will receive the updated state.

[match endMatchInTurnWithMatchData:matchData

completionHandler:^(NSError *error) {

// We're done telling Game Center that the match is done

}];

Additionally, the current player can update the game state without ending his turn. If he does this, all other players get notified of the new game state:

GKTurnBasedMatch* match = ... // the current match

NSData* matchData = ... // the current game state

// Tell other players about the new state of the game. It still remains

// the current player's turn.

[match saveCurrentTurnWithMatchData:matchData

completionHandler:^(NSError *error) {

// We're done sending the match data

}];

This can be used if the player decides to do something that’s visible to all other players; for example, if the player decides to buy a house in a game like Monopoly, all other players should be notified of this so that they can adjust their strategy. However, buying a house doesn’t end the player’s turn.

Discussion

Turn-based games are hosted by Game Center. In a turn-based game, the entire state of the game is wrapped up in an NSData object that’s maintained by Game Center. When it’s a given player’s turn, your game retrieves the game state from the Game Center server and lets the player decide what she’d like to do with her turn. When the player’s done, your game produces an NSData object that represents the new game state, which takes into account the decisions the player has made. It then sends it to Game Center, along with information on which player should play next.

It’s possible for a player to be involved in multiple turn-based games at the same time. This means that even when it isn’t the player’s turn in a particular match, she can still play your game. When you present a GKTurnBasedMatchmakerViewController, the view controller that’s displayed shows all current games that the player can play. When the player selects a match, your code receives the GKTurnBasedMatch object that represents the current state of the game.

If the player creates a new match, Game Center finds participants for the match based on the GKMatchRequest that you pass in, and tells its delegate about the GKTurnBasedMatch object. Alternatively, if the player selects a match that she’s currently playing in, the view controller gives its delegate the appropriate GKTurnBasedMatch.

It’s important to let the player know when it’s her turn. When your code gets notified that it’s the player’s turn in any of the matches that she’s playing in, your code should let the player know as soon as possible.

Sharing Text and Images to Social Media Sites

Problem

You want to allow your users to send tweets or post to Facebook from your games, so that they can share their high scores and characters.

Solution

The SLComposeViewController is available as part of Apple’s Social framework. To use it, you’ll need to add Social.framework to your project and import Social/Social.h in the view controller where you want to use it. You can then use code similar to the following to allow the user to send a tweet containing nothing but text:

SLComposeViewController *tweetSheet =

[SLComposeViewController

composeViewControllerForServiceType:SLServiceTypeTwitter];

[tweetSheet setInitialText:@"My high score was legendary!"];

[self presentViewController:tweetSheet animated:YES completion:Nil];

The first line creates an SLComposeViewController (also known as a “tweet sheet”) with the service type set to Twitter, and the last line displays it. The second line is where you can set a string to be sent as the tweet; the user will be able to edit or remove this text once the view is presented.

You can also attach an image or a URL to a tweet:

[tweetSheet addImage:[UIImage imageNamed:@"Apollo"]];

[tweetSheet addURL:[NSURL URLWithString:@"http://www.secretlab.com.au"]];

The addImage: method takes a UIImage as a parameter, in this case referring to an image inside an asset catalog. The addURL: method takes an NSURL as a parameter; in this case, we’re creating a new URL from a string.

Allowing your users to post to Facebook is similar, but when you create the SLComposeViewController, instead of setting the service type to Twitter you set it to Facebook:

SLComposeViewController *facebookSheet = [SLComposeViewController

composeViewControllerForServiceType:SLServiceTypeFacebook];

As with Twitter, posting to Facebook supports the addition of a URL and an image. The SLComposeViewController also supports posting to Sina Weibo. You can check if a particular social service is available using the SLComposeViewController. For example:

if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])

{

// We know that Twitter is available

}

else

{

// We know that Twitter is not available

}

This code checks with the SLComposeViewController for the availability of SLServiceTypeTwitter, allowing you to perform alternate actions if the user has not added any social accounts of the type that you are attempting to use.

Discussion

Allowing your users to push content or high scores from your game out to their social networks is a great way to spread the word about what you’ve built. iOS makes it easy to attach content and links to social network posts, as you can see.

Implementing iOS Networking Effectively

Problem

You want to make effective and compelling use of the networking features of iOS in your game.

Solution

There’s one very simple rule you can follow to make a great iOS game with networking: use as much of the provided game-network infrastructure as possible!

By tying your game to the features that are provided by iOS, such as Game Center, you’re ensuring that your game will work properly alongside future versions of iOS, and that players will already know how to work many of the network-based components of your game through their exposure to other games.

Discussion

By adhering to Apple’s recommended standards and techniques for building networked games, you’ll create experiences that users already know how to use and understand. Don’t be tempted to deviate! Your users will suffer because of it.

Implementing Social Networks Effectively

Problem

You want to make sure that your game makes effective use of the social features of iOS.

Solution

The solution here is simple: don’t go overboard with social. It’s very tempting to encourage users to tweet and post status updates about your game at every turn. After all, you have near-unfettered access to their social networking accounts, so why not encourage them to post constantly?

Here’s a tip: you should use these great powers only sparingly. Users don’t want to spam their friends, and they probably don’t want to talk about a game they’re not enjoying—and you don’t want them to do these things either! Offer the ability to tweet and post status updates at junctures in your game when your users are likely to be at their happiest. Did they just win a level? Offer them the ability to tweet it. Did they just lose? Don’t offer them the ability to tweet it.

Discussion

While it’s tempting to put links to social media everywhere, it really only works best when it’s exposed to happy, fulfilled users. Users who are bored or have just lost a game are likely to tweet (at best) meaningless spam or (at worst) critical comments about your game. Don’t encourage them—choose your moments carefully.