Update 3/1/14: Updated for iOS 7 and Sprite Kit.
This is the second part of a two part Game Center tutorial series on creating a simple networked game with Game Center matchmaking.
In the first part of the Game Center tutorial series, you learned how to enable Game Center for your app and find matches with other players using the built-in GKMatchmakerViewController
.
In this second and final part of the Game Center tutorial series, you’ll learn how to look up player aliases and how to send and receive data between devices.
In the end, you’ll have a fully functional (but simple) networked game that uses SpriteKit and Game Center that you can play with your friends!
Start by downloading the starter project for part 2 of this tutorial. Even though this tutorial is a continuation of the previous Game Center tutorial, I’ve built a slightly modified version of the final project of the last tutorial. Don’t worry, there are hardly any changes – just a few small things to keep the focus of the tutorial squarely on Game Center. I’ll describe the changes in more detail in the next section.
Multiplayer Starter Project
First things first – as soon as you open the starter project, open your target settings and update your Bundle Identifier to whatever you set last time. This is important, so it might be a good idea to open your old project, and double check it matches exactly. Then restart Xcode so the changes are sure to be applied.
Next, let’s take a moment and go through some of the changes I made to the project from the previous tutorial.
- Open GameViewController.m: you will notice that this class no longer implements the
GameKitHelperDelegate
protocol. - In the GameKit group you will notice a new class i.e.
MultiplayerNetworking
. This class will contain all the networking code for the game. It will notify the game of important events through a delegate. GameScene
now implements theMultiplayerNetworkingProtocol
.- Since the
MultiplayerNetworking
class is responsible for managing all networking activity, naturally it will implement theGameKitHelperDelegate
protocol.
Those are all the changes to the final project of the last tutorial. Before you proceed make sure that you go through the code a couple of times, so that you’re comfortable with the changes.
Finally, run the project on two devices and tap “Play Now” to find a match. Verify that everything works as before, and that it finds a match and displays this in the console:
CatRaceStarter[3712:60b] Ready to start match!
Network Code Strategy: Picking Players
Let’s talk about the strategy you’re going to take with the network code, starting with how to pick players.
You have two players in this game: player 1 (the dog) and player 2 (the kid). The problem is, how do you decide who should be the dog and which the kid?
The strategy you’re going to take is each side will generate a random number on startup, and send it to the other side. Whichever side has the larger random number will be player 1, and the other side will be player 2.
In the rare event each side generates the same random number, you’ll just try again.
Whichever player is player 1 will get some special “privileges.” First, that side signals the game should start by sending a message to the other side. That side also is the one who is responsible for checking when the game ends, and sending a message to the other side when the game is over (and who won).
In other words, “player 1″ takes the role of the “server”. He’s the final authority on what goes on for this game.
Network Code Strategy: Player Aliases
Since it’s random which player is the dog and which is the kid, you need to have some way of showing the player which one he is.
For this game, you’ll just print the player’s alias right on top of the character they are, so they can tell who they are.
If you don’t know what a player alias is, it’s the nickname that you set up for your account in Game Center. When a match is made, you don’t get these automatically – you have to call a method so Game Center will send them to you.
Network Code Strategy: Game States
One of the challenges with making networked code is that things can happen in different orders than you might expect.
For example, one side may finish initializing the match and send the random number to the other side before the other side even finishes initializing the match!
So if you’re not careful, you can get into strange timing issues in your code that causes problems. One good way to keep things clean is to keep careful track of what state each side of the game is currently in.
The following diagram illustrates the states Cat Race will need:
Let’s go through each of these:
- Waiting for Match: The game is waiting for a match to be connected, and for the player aliases to be looked up. When both of these are complete, it checks to see if it’s received the random number from the other side yet, and advances to the next state appropriately.
- Waiting for Random #: The game has a match and has the player aliases, but is still waiting to receive a random number from the other side.
- Waiting for Start: The game is waiting for the other side to start the game (this occurs when the local player is player 2).
- Active: The game is currently playing – no winner has been found yet. Every time a player moves, they will tell the other side that they’ve moved by sending a message.
- Done: The game is over (player 1 has sent a message to player 2 to tell him that the game is over). No more messages are sent and the player can then restart the game.
OK now that you have a plan in mind, let’s move onto the first step, which is retrieving the player aliases!
Looking up Player Aliases
Switch to GameKitHelper.h and make the following changes:
// Add after @interface @property (nonatomic, strong) NSMutableDictionary *playersDict; |
This defines a property for a dictionary that will allow easy lookup of GKPlayer
data (which includes the player’s alias) based on a player’s unique ID.
Then switch to GameKitHelper.m and make the following changes:
// Add new method after authenticateLocalPlayer - (void)lookupPlayers { NSLog(@"Looking up %lu players...", (unsigned long)_match.playerIDs.count); [GKPlayer loadPlayersForIdentifiers:_match.playerIDs withCompletionHandler:^(NSArray *players, NSError *error) { if (error != nil) { NSLog(@"Error retrieving player info: %@", error.localizedDescription); _matchStarted = NO; [_delegate matchEnded]; } else { // Populate players dict _playersDict = [NSMutableDictionary dictionaryWithCapacity:players.count]; for (GKPlayer *player in players) { NSLog(@"Found player: %@", player.alias); [_playersDict setObject:player forKey:player.playerID]; } [_playersDict setObject:[GKLocalPlayer localPlayer] forKey:[GKLocalPlayer localPlayer].playerID]; // Notify delegate match can begin _matchStarted = YES; [_delegate matchStarted]; } }]; } // Add inside matchmakerViewController:didFindMatch, right after @"Ready to start match!": [self lookupPlayers]; // Add inside match:player:didChangeState:, right after @"Ready to start match!": [self lookupPlayers]; |
lookupPlayers
is the main method here. This is called when the match is ready, and it looks up the info for all of the players currently in the match. It also adds the local player’s information to the dictionary.
Game Center will return a GKPlayer
object for each player in the match as a result. To make things easier to access later, this code puts each GKPlayer
object in a dictionary, keyed by player ID.
Finally, it marks the match as started and calls the game’s delegate so it can start the game.
Before you move onto that though, test it out! Compile and run your code on two devices, and this time when you examine your console output you should see the players looked up and the game ready to go:
2014-01-06 21:50:13.867 CatRaceStarter[787:60b] Ready to start match! 2014-01-06 21:50:13.874 CatRaceStarter[787:60b] Looking up 1 players... 2014-01-06 21:50:13.894 CatRaceStarter[787:60b] Found player: Olieh 2014-01-06 21:50:13.895 CatRaceStarter[787:60b] Match has started successfully
Adding Network Code
You have the match set up and the player names available, so now you’re on to the real meat of the project – adding the network code!
The first thing you need to do is define game states based on our diagram from earlier. Open up MultiplayerNetworking.m and add the following:
//Add to the top of the file typedef NS_ENUM(NSUInteger, GameState) { kGameStateWaitingForMatch = 0, kGameStateWaitingForRandomNumber, kGameStateWaitingForStart, kGameStateActive, kGameStateDone }; |
Next you need to define some structures for the messages you’ll be sending back and forth. So add the following to the top of the same file:
typedef NS_ENUM(NSUInteger, MessageType) { kMessageTypeRandomNumber = 0, kMessageTypeGameBegin, kMessageTypeMove, kMessageTypeGameOver }; typedef struct { MessageType messageType; } Message; typedef struct { Message message; uint32_t randomNumber; } MessageRandomNumber; typedef struct { Message message; } MessageGameBegin; typedef struct { Message message; } MessageMove; typedef struct { Message message; BOOL player1Won; } MessageGameOver; |
Note that each message starts with a message type – this is so that you have a known number you can look at for each message to identify what type of message it is.
Finally add a few instance variables to the implementation section of the MutliplayerNetorking
class:
@implementation MultiplayerNetworking { uint32_t _ourRandomNumber; GameState _gameState; BOOL _isPlayer1, _receivedAllRandomNumbers; NSMutableArray *_orderOfPlayers; }; |
These will keep track of the random number for the local device, whether all the random numbers have been received and the order of players in the match. The order of the players will be decided on the random number each player generates, as per our strategy.
OK, now let’s start implementing the networking code. Modify the matchStarted
method and also add two stubs methods as follows:
- (void)matchStarted { NSLog(@"Match has started successfully"); if (_receivedAllRandomNumbers) { _gameState = kGameStateWaitingForStart; } else { _gameState = kGameStateWaitingForRandomNumber; } [self sendRandomNumber]; [self tryStartGame]; } - (void)sendRandomNumber { } - (void)tryStartGame { } |
matchStarted
first checks if the game has received random numbers from all players of the match. If it has, then it moves the game state to the “waiting for start” state.
Before you fill out the stub methods, you need to initialize the game state variable and generate a random number. Make the following changes to MultiplayerNetworking.m:
//Add to the top of the file #define playerIdKey @"PlayerId" #define randomNumberKey @"randomNumber" //Add to implementation section - (id)init { if (self = [super init]) { _ourRandomNumber = arc4random(); _gameState = kGameStateWaitingForMatch; _orderOfPlayers = [NSMutableArray array]; [_orderOfPlayers addObject:@{playerIdKey : [GKLocalPlayer localPlayer].playerID, randomNumberKey : @(_ourRandomNumber)}]; } return self; } |
Next, add a method to send data to all member of the match.
- (void)sendData:(NSData*)data { NSError *error; GameKitHelper *gameKitHelper = [GameKitHelper sharedGameKitHelper]; BOOL success = [gameKitHelper.match sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error]; if (!success) { NSLog(@"Error sending data:%@", error.localizedDescription); [self matchEnded]; } } |
The sendData:
method uses the sendDataToAllPlayers:withDataMode:error:
of the GKMatch
object to send data to all players of the match. Using GKMatchSendDataReliable
ensures that the data sent will be received by all other players of the game. Of Course the players need to be connected to the network at all times for this to work, else the game would just end.
Armed with the power to send messages to other players, add the following code to the sendRandomNumber
method.
MessageRandomNumber message; message.message.messageType = kMessageTypeRandomNumber; message.randomNumber = _ourRandomNumber; NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageRandomNumber)]; [self sendData:data]; |
The above code creates a new MessageRandomNumber
structure, and sets the random number to the one that was generated in init
. It then converts the structure into NSData
to send to the other side.
Next, implement the tryStartGame
method you referred to earlier. Make the following changes to MultiplayerNetworking.m:
// Add right after sendRandomNumber - (void)sendGameBegin { MessageGameBegin message; message.message.messageType = kMessageTypeGameBegin; NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageGameBegin)]; [self sendData:data]; } // Fill the contents of tryStartGame as shown - (void)tryStartGame { if (_isPlayer1 && _gameState == kGameStateWaitingForStart) { _gameState = kGameStateActive; [self sendGameBegin]; } } |
This is quite simple – if it’s player 1 (who has the special privilege of acting like the “server”) and the game is ready to go, it sets the game to active, and tells the other side to do the same by sending a MessageGameBegin
to the other side.
Build and run on two devices and create a match as you did before. If you everything goes well you should not see any errors on the console and have the following logs printed:
2014-01-06 23:13:19.846 CatRaceStarter[814:60b] Ready to start match! 2014-01-06 23:13:19.848 CatRaceStarter[814:60b] Looking up 1 players... 2014-01-06 23:13:19.873 CatRaceStarter[814:60b] Found player: Tapstudio 2014-01-06 23:13:19.874 CatRaceStarter[814:60b] Match has started successfully |
OK – now for the code that handles receiving messages from the other side. Add the following stub methods to the MultiplayerNetworking.m file.
-(void)processReceivedRandomNumber:(NSDictionary*)randomNumberDetails { } - (BOOL)isLocalPlayerPlayer1 { return NO; } |
You will be filling these out in just a moment. Next, modify your match:didReceiveData:fromPlayer:
method to handle the random number message:
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID { //1 Message *message = (Message*)[data bytes]; if (message->messageType == kMessageTypeRandomNumber) { MessageRandomNumber *messageRandomNumber = (MessageRandomNumber*)[data bytes]; NSLog(@"Received random number:%d", messageRandomNumber->randomNumber); BOOL tie = NO; if (messageRandomNumber->randomNumber == _ourRandomNumber) { //2 NSLog(@"Tie"); tie = YES; _ourRandomNumber = arc4random(); [self sendRandomNumber]; } else { //3 NSDictionary *dictionary = @{playerIdKey : playerID, randomNumberKey : @(messageRandomNumber->randomNumber)}; [self processReceivedRandomNumber:dictionary]; } //4 if (_receivedAllRandomNumbers) { _isPlayer1 = [self isLocalPlayerPlayer1]; } if (!tie && _receivedAllRandomNumbers) { //5 if (_gameState == kGameStateWaitingForRandomNumber) { _gameState = kGameStateWaitingForStart; } [self tryStartGame]; } } } |
This method casts the incoming data as a Message structure (which always works, because you’ve set up each structure to begin with a Message structure). It can then look at the message type to see which type of message it actually is. Let’s go through it step by step.
- The received data is first cast into a
MessageStruct
. - The received number is compared with the locally generated number. In case there is a tie you regenerate the random number and send again.
- If the random number received is not the same as the locally generated one, the method creates a dictionary that stores the player id and the random number it generated.
- When all random numbers are received and the order of players has been determined the
_receivedAllRandomNumbers
variable will be true. In this case you check if the local player is player 1. - Finally if it wasn’t a tie and the local player is player 1 you initiate the game. Else you move the game state to “waiting for start”.
You now have all the code in place to receive and send the random number message. However, you still need to process the incoming random number an arrange the players in order. The players will be arranged on the basis on the random number they generate. The one that has the highest is player 1, the one next in line is player 2 and so on. Fill the processReceivedRandomNumber:
method with the following code:
//1 if([_orderOfPlayers containsObject:randomNumberDetails]) { [_orderOfPlayers removeObjectAtIndex: [_orderOfPlayers indexOfObject:randomNumberDetails]]; } //2 [_orderOfPlayers addObject:randomNumberDetails]; //3 NSSortDescriptor *sortByRandomNumber = [NSSortDescriptor sortDescriptorWithKey:randomNumberKey ascending:NO]; NSArray *sortDescriptors = @[sortByRandomNumber]; [_orderOfPlayers sortUsingDescriptors:sortDescriptors]; //4 if ([self allRandomNumbersAreReceived]) { _receivedAllRandomNumbers = YES; } |
The above code is quite easy to understand so I won’t go into the details. All you’re doing here is storing the received data in an array and sorting that array based on the random number.
At this point Xcode will show an error. Thats because it can’t find the method named allRandomNumbersAreReceived
. Let’s go ahead and add that in.
- (BOOL)allRandomNumbersAreReceived { NSMutableArray *receivedRandomNumbers = [NSMutableArray array]; for (NSDictionary *dict in _orderOfPlayers) { [receivedRandomNumbers addObject:dict[randomNumberKey]]; } NSArray *arrayOfUniqueRandomNumbers = [[NSSet setWithArray:receivedRandomNumbers] allObjects]; if (arrayOfUniqueRandomNumbers.count == [GameKitHelper sharedGameKitHelper].match.playerIDs.count + 1) { return YES; } return NO; } |
The above method is just a helper method, it returns a boolean which is true when all random numbers have been received and are unique.
Remember the isLocalPlayerPlayer1
method you added before. Fill that out with the following lines of code:
NSDictionary *dictionary = _orderOfPlayers[0]; if ([dictionary[playerIdKey] isEqualToString:[GKLocalPlayer localPlayer].playerID]) { NSLog(@"I'm player 1"); return YES; } return NO; |
Build and run the game on two separate devices. You must know the drill by now. This time each device will send out a random number and one will be selected as player 1. Player 1′s device will show the following logs:
2014-01-07 00:35:10.774 CatRaceStarter[859:60b] Ready to start match! 2014-01-07 00:35:10.777 CatRaceStarter[859:60b] Looking up 1 players... 2014-01-07 00:35:10.808 CatRaceStarter[859:60b] Found player: Tapstudio 2014-01-07 00:35:10.809 CatRaceStarter[859:60b] Match has started successfully 2014-01-07 00:35:10.848 CatRaceStarter[859:60b] Received random number:-1453704186 2014-01-07 00:35:10.850 CatRaceStarter[859:60b] I'm player 1 |
Awesome! Your code can handle the random number message and decide the order or players. You still however need to handle the other types of messages. Append the following code to the match:didReceiveData:fromPlayer:
method:
else if (message->messageType == kMessageTypeGameBegin) { NSLog(@"Begin game message received"); _gameState = kGameStateActive; } else if (message->messageType == kMessageTypeMove) { NSLog(@"Move message received"); MessageMove *messageMove = (MessageMove*)[data bytes]; } else if(message->messageType == kMessageTypeGameOver) { NSLog(@"Game over message received"); } |
Let’s pause for a moment and think about how these messages will be handled. Since the MultiplayerNetworking
class is responsible for receiving and processing these messages and the GameScene
is responsible for rendering the game, these two need to communicate with each other. Hence the MultiplayerNetworkingProtocol
.
Add the following method to the MultiplayerNetworkingProtocol
present in MultiplayerNetworking.h:
- (void)setCurrentPlayerIndex:(NSUInteger)index; |
Next, switch to MultiplayerNetworking.m and add a call to the above method in the tryStartGame
method:
- (void)tryStartGame { if (_isPlayer1 && _gameState == kGameStateWaitingForStart) { _gameState = kGameStateActive; [self sendGameBegin]; //first player [self.delegate setCurrentPlayerIndex:0]; } } |
Since the begin game message is only going to be sent by Player 1. The MultiplayerNetworking
class can safely notify the GameScene
that the local player is Player 1.
The above will work when the local player is player 1. What if the local player is not player 1, in that case you will need to find out the index of the player from the _orderOfPlayer
array and notify the GameScene
which player belongs to him/her.
Add the following helper methods to MultiplayerNetworking.m:
- (NSUInteger)indexForLocalPlayer { NSString *playerId = [GKLocalPlayer localPlayer].playerID; return [self indexForPlayerWithId:playerId]; } - (NSUInteger)indexForPlayerWithId:(NSString*)playerId { __block NSUInteger index = -1; [_orderOfPlayers enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop){ NSString *pId = obj[playerIdKey]; if ([pId isEqualToString:playerId]) { index = idx; *stop = YES; } }]; return index; } |
The above methods simply finds the index of the local player based on their player ID. In case the local player is not Player 1, he/she will receive a begin game message. This is the right place to find of the index of the local player and inform the GameScene
. Append the following code to the section in which the game begin message is handled in the match:didReceivedData:fromPlayer:
method:
[self.delegate setCurrentPlayerIndex:[self indexForLocalPlayer]]; |
With all that in place, open GameScene.m
and implement the setCurrentPlayerIndex:
method:
- (void)setCurrentPlayerIndex:(NSUInteger)index { _currentPlayerIndex = index; } |
Build and run. Now when the game starts you’ll notice that each device is assigned a player, tap on the screen to move your player ahead. You’ll notice that as the local player moves ahead their position on the other player’s device does not change.
Now let’s add code to send a move message when the local player taps the screen. For this, add the following method to MultiplayerNetworking.m:
- (void)sendMove { MessageMove messageMove; messageMove.message.messageType = kMessageTypeMove; NSData *data = [NSData dataWithBytes:&messageMove length:sizeof(MessageMove)]; [self sendData:data]; } |
Define the above method in the MultiplayerNetworking
interface in MultiplayerNetworking.h as shown below:
- (void)sendMove; |
Now that you can send a move message, switch to GameScene.m and add a call to the above method in the touchesBegan:withEvent:
method as shown below:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (_currentPlayerIndex == -1) { return; } [_players[_currentPlayerIndex] moveForward]; [_networkingEngine sendMove]; } |
This will handle the sending part of the equation, however you still need to add code to receive the message and take an appropriate action. For this add the following method declaration to the MultiplayerNetworkingProtocol
in MultiplayerNetworking.h:
- (void)movePlayerAtIndex:(NSUInteger)index; |
Next, add a call to the above method at the end of the section that handles “move messages” in the match:didReceiveData:fromPlayer:
.
[self.delegate movePlayerAtIndex:[self indexForPlayerWithId:playerID]]; |
Open GameScene.m and add the implementation of the above method at the end of the file as shown below:
- (void)movePlayerAtIndex:(NSUInteger)index { [_players[index] moveForward]; } |
Build and run. As you tap the screen you will notice that your player moves forward not only on your device but on your opponent’s as well.
Ending the Match
At this point you can play the game on two devices however you have not added any end condition. Your game needs to be in a position to end the game and determine a winner. To do this make the following changes to the MultiplayerNetworking
class.
//Add to MultiplayerNetworking.m - (void)sendGameEnd:(BOOL)player1Won { MessageGameOver message; message.message.messageType = kMessageTypeGameOver; message.player1Won = player1Won; NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageGameOver)]; [self sendData:data]; } //Declare the above method in MultiplayerNetworking.h - (void)sendGameEnd:(BOOL)player1Won; |
Switch to GameScene.m and make the following changes to the update:
method:
-(void)update:(CFTimeInterval)currentTime { if (self.paused && _currentPlayerIndex == -1) { return; } //Only Player 1 will check for game over condition if (_currentPlayerIndex == 0) { // Here! [_players enumerateObjectsUsingBlock:^(PlayerSprite *playerSprite, NSUInteger idx, BOOL *stop) { if(playerSprite.position.x + playerSprite.size.width/2 > _cat.position.x) { BOOL didWin = NO; if (idx == _currentPlayerIndex) { NSLog(@"Won"); didWin = YES; } else { //you lost NSLog(@"Lost"); } self.paused = YES; _currentPlayerIndex = -1; *stop = YES; [_networkingEngine sendGameEnd:didWin]; // Here! if (self.gameOverBlock) { self.gameOverBlock(didWin); } } }]; } // Here! } |
The way the above code works is that the game first checks if the local player is player1. If yes it enumerates through each player and checks to see if any player has crossed the cat. If a player has crossed the cat player 1 sends out the game over message.
Next you need add code to receive the game over message and show the user the game over view. To do this make the following changes:
// Add to MultiplayerNetworkingProtocol in MultiplayerNetworking.h - (void)gameOver:(BOOL)player1Won; // Add to the end of the section that handles the game over message in match:didReceiveData:fromPlayer: in MultiplayerNetworking.m MessageGameOver * messageGameOver = (MessageGameOver *) [data bytes]; [self.delegate gameOver:messageGameOver->player1Won]; // Add the new method to GameScene.m - (void)gameOver:(BOOL)player1Won { BOOL didLocalPlayerWin = YES; if (player1Won) { didLocalPlayerWin = NO; } if (self.gameOverBlock) { self.gameOverBlock(didLocalPlayerWin); } } |
Build and run. The game should now show you a game complete screen like the one shown below. Hurray! you now have a fully functional multiplayer networked game.
Displaying Player Names
At this point you have a functional game, but it’s kinda hard to tell which player you are, especially since it’s random.
So let’s fix this by displaying the player names for each player above their character. Make the following changes to PlayerSprite.m:
//Add to the top of the file #define kPlayerAliasLabelName @"player_alias" //modify the initWithType: method - (instancetype)initWithType:(enum PlayerSpriteType)playerType { . . . self = [super initWithTexture:[gameAtlas textureNamed:textureName]]; if (self) { . . . SKLabelNode *playerAlias = [[SKLabelNode alloc] initWithFontNamed:@"Arial"]; playerAlias.fontSize = 20; playerAlias.fontColor = [SKColor redColor]; playerAlias.position = CGPointMake(0, 40); playerAlias.name = kPlayerAliasLabelName; [self addChild:playerAlias]; _moveAnimation = [SKAction repeatActionForever:[SKAction animateWithTextures:textures timePerFrame:0.1]]; } return self; } //Add new method at the bottom - (void)setPlayerAliasText:(NSString*)playerAlias; { SKLabelNode *playerAliasLabel = (SKLabelNode*)[self childNodeWithName:kPlayerAliasLabelName]; playerAliasLabel.text = playerAlias; } |
Then open PlayerSprite.h and declare the new method as shown below:
- (void)setPlayerAliasText:(NSString*)playerAlias; |
With the above code you have added a label to PlayerSprite
which will be displayed above the character. Also you’ve added a convenience method to set the text on that label.
Next, make the following changes to the MultiplayerNetworking
class as shown below:
//Add to MultiplayerNetworkingProtocol - (void)setPlayerAliases:(NSArray*)playerAliases; //Add to MultiplayerNetworking.m - (void)processPlayerAliases { if ([self allRandomNumbersAreReceived]) { NSMutableArray *playerAliases = [NSMutableArray arrayWithCapacity:_orderOfPlayers.count]; for (NSDictionary *playerDetails in _orderOfPlayers) { NSString *playerId = playerDetails[playerIdKey]; [playerAliases addObject:((GKPlayer*)[GameKitHelper sharedGameKitHelper].playersDict[playerId]).alias]; } if (playerAliases.count > 0) { [self.delegate setPlayerAliases:playerAliases]; } } } //Modify the tryStartGame method - (void)tryStartGame { if (_isPlayer1 && _gameState == kGameStateWaitingForStart) { . . . [self processPlayerAliases]; } } //Modify the case that handles the game begin message in match:didReceiveData:fromPLayer: else if (message->messageType == kMessageTypeGameBegin) { . . . [self processPlayerAliases]; } |
Basically when the game is transitioning to the Active state, you know for sure you’ve received the player names and the players have been ordered as per the random numbers they’ve generated. So that’s a good time to set up the tell the delegate to display player aliases.
You now need to implement the setPlayerAliases:
method in the GameScene
class. Add the following code to GameScene.m:
- (void)setPlayerAliases:(NSArray*)playerAliases { [playerAliases enumerateObjectsUsingBlock:^(NSString *playerAlias, NSUInteger idx, BOOL *stop) { [_players[idx] setPlayerAliasText:playerAlias]; }]; } |
Compile and run your code on two devices, and now you should see the label of each player’s name on top of their character!
Exercise: Supporting Invites
If you’ve got this far then you must have gotten the hang of how Game Center APIs work and how to design your game to work with them. As a last feature you’re going to add support for invites.
While working with the matchmaker view you must have noticed that it gives you an option to send an invite to a friend. Right now when you select this all that will happen is that a notification will be shown on your friend’s device, when he/she selects the notification it will simply launch the game and nothing else. Ideally the game should start and create a match with the player who sent the invite.
Doing this is very simple, hence you’re going to implement this on your own. I’ve listed the steps you need to follow to get this working:
- Implement the
GKLocalPlayerListener
protocol and set theGameKitHelper
class as a delegate using theregisterListener:
method ofGKLocalPlayer
. This should be done as soon as the local player is authenticated. - Implement the
player:didAcceptInvite:
method and theplayer:didRequestMatchWithPlayers:
method of theGKLocalPlayerListener
protocol. - The methods mentioned in step 2 will be invoked when the user receives an invite. Store the details you receive here in an instance variable.
- Since you’ve store the details of the invite received, modify the
findMatchWithMinPlayers:maxPlayers:viewController:delegate:
method to use these details. Use theinitWithInvite:
method of theGKMatchMakerViewController
to initiate a match with the player who sent the invite.
Where To Go From Here?
Here is the final project with the completed SpriteKit and Game Center multiplayer networked game you created in this tutorial series. I hope this tutorial gives you a good point to start in creating amazing multiplayer games.
If you’re implementing Game Center in your games, you might want to add Leaderboards and Achievements too. If you’re interested in learning about this, check out the chapters on Game Center in our book iOS Games by Tutorials.
If you have any questions, comments, or tips for using Game Center that you’ve learned while making your apps, please join in the forum discussion below!
Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 2/2 is a post from: Ray Wenderlich
The post Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 2/2 appeared first on Ray Wenderlich.