Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 1/2
Learn how to make a simple multiplayer racing game with Sprite Kit in this Game Center tutorial! By Ali Hafizji.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 1/2
25 mins
- Getting Started
- Enabling Game Center: Overview
- Enabling Game Center for your app
- Register your app in iTunes Connect
- Authenticate the Local Player: Strategy
- Authenticate the Local User: Implementation
- Adding Game Center authentication to CatRace
- Matchmaker, Matchmaker, Make Me A Match
- Where To Go From Here?
Matchmaker, Matchmaker, Make Me A Match
There are two ways to find someone to play with via Game Center: search for match programatically, or use the built-in matchmaking user interface.
In this tutorial, you’re going to use the built-in matchmaking user interface. The idea is when you want to find a match, you set up some parameters in a GKMatchRequest
object, then create and display an instance of a GKMatchmakerViewController
.
Let’s see how this works. First make a few changes to GameKitHelper.h:
// Add to top of file right after the @import
@protocol GameKitHelperDelegate
- (void)matchStarted;
- (void)matchEnded;
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data
fromPlayer:(NSString *)playerID;
@end
// Modify @interface line to support protocols as follows
@interface GameKitHelper : NSObject <GKMatchmakerViewControllerDelegate, GKMatchDelegate>
// Add after @interface
@property (nonatomic, strong) GKMatch *match;
@property (nonatomic, assign) id <GameKitHelperDelegate> delegate;
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GameKitHelperDelegate>)delegate;
There’s a bunch of new stuff here, so let’s go over it bit by bit.
- You define a protocol called
GameKitHelperDelegate
that you’ll use to notify other objects of when important events happen, such as the match starting, ending, or receiving data from the other party. For now, theGameViewController
will be implementing this protocol. - The
GameKitHelper
object is marked as implementing two protocols. The first is so that the matchmaker user interface can notify this object when a match is found or not. The second is so that Game Center can notify this object when data is received or the connection status changes. - Creates a new method that the
GameViewController
will call to look for someone to play with.
Next switch to GameKitHelper.m and make the following changes:
// Add a new private variable to the implementation section
BOOL _matchStarted;
// Add new method, right after authenticateLocalUser
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GameKitHelperDelegate>)delegate {
if (!_enableGameCenter) return;
_matchStarted = NO;
self.match = nil;
_delegate = delegate;
[viewController dismissViewControllerAnimated:NO completion:nil];
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
GKMatchmakerViewController *mmvc =
[[GKMatchmakerViewController alloc] initWithMatchRequest:request];
mmvc.matchmakerDelegate = self;
[viewController presentViewController:mmvc animated:YES completion:nil];
}
This is the method that the view controller will call to find a match. It does nothing if Game Center is not available.
It initializes the match as not started yet, and the match object as nil. It stores away the delegate for later use, and dismisses any previously existing view controllers (in case a GKMatchmakerViewController
is already showing).
Then it moves into the important stuff. The GKMatchRequest
object allows you to configure the type of match you’re looking for, such as a minimum and maximum amount of players. This method sets it to whatever is passed in (which for this game will be min 2, max 2 players).
Next it creates a new instance of the GKMatchmakerViewController
with the given request, sets its delegate to the GameKitHelper
object, and uses the passed-in view controller to show it on the screen.
The GKMatchmakerViewController
takes over from here, and allows the user to search for a random player and start a game. Once it’s done some callback methods will be called, so let’s add those next:
// The user has cancelled matchmaking
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
}
// Matchmaking has failed with an error
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error {
[viewController dismissViewControllerAnimated:YES completion:nil];
NSLog(@"Error finding match: %@", error.localizedDescription);
}
// A peer-to-peer match has been found, the game should start
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match {
[viewController dismissViewControllerAnimated:YES completion:nil];
self.match = match;
match.delegate = self;
if (!_matchStarted && match.expectedPlayerCount == 0) {
NSLog(@"Ready to start match!");
}
}
If the user cancelled finding a match or there was an error, it just closes the matchmaker view.
However if a match was found, it squirrels away the match object and sets the delegate of the match to be the GameKitHelper
object so it can be notified of incoming data and connection status changes.
It also runs a quick check to see if it’s time to actually start the match. The match object keeps track of how many players still need to finish connecting as the expectedPlayerCount
.
If this is 0, everybody’s ready to go. Right now you’re just going to log that out – later on you’ll actually do something interesting here.
Next, add the implementation of the GKMatchDelegate
callbacks:
#pragma mark GKMatchDelegate
// The match received data sent from the player.
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
if (_match != match) return;
[_delegate match:match didReceiveData:data fromPlayer:playerID];
}
// The player state changed (eg. connected or disconnected)
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (_match != match) return;
switch (state) {
case GKPlayerStateConnected:
// handle a new player connection.
NSLog(@"Player connected!");
if (!_matchStarted && match.expectedPlayerCount == 0) {
NSLog(@"Ready to start match!");
}
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
NSLog(@"Player disconnected!");
_matchStarted = NO;
[_delegate matchEnded];
break;
}
}
// The match was unable to connect with the player due to an error.
- (void)match:(GKMatch *)match connectionWithPlayerFailed:(NSString *)playerID withError:(NSError *)error {
if (_match != match) return;
NSLog(@"Failed to connect to player with error: %@", error.localizedDescription);
_matchStarted = NO;
[_delegate matchEnded];
}
// The match was unable to be established with any players due to an error.
- (void)match:(GKMatch *)match didFailWithError:(NSError *)error {
if (_match != match) return;
NSLog(@"Match failed with error: %@", error.localizedDescription);
_matchStarted = NO;
[_delegate matchEnded];
}
match:didReceiveData:fromPlayer:
method is called when another player sends data to you. This method simply forwards the data onto the delegate, so that it can do the game-specific stuff with it.
For match:player:didChangeState:
, when the player connects you need to check if all the players have connected in, so you can start the match once they’re all in. Other than that, if a player disconnects it sets the match as ended and notifies the delegate.
The final two methods are called when there’s an error with the connection. In either case, it marks the match as ended and notifies the delegate.
OK, now that you have this code to establish a match, let’s use it in our GameViewController
. For the matchmaker view controller to show up it is necessary that the local player is authenticated and since authenticating a local player is an asynchronous process the GameViewController
needs to be notified in some way when the user is authenticated. To do this you’re going to use good old notifications. Still in GameKitHelper.m, make the following changes:
// Add to the top of the file
NSString *const LocalPlayerIsAuthenticated = @"local_player_authenticated";
// Add this between the 1st and 2nd step of authenticateLocalPlayer
if (localPlayer.isAuthenticated) {
[[NSNotificationCenter defaultCenter] postNotificationName:LocalPlayerIsAuthenticated object:nil];
return;
}
// Modify the 5th step of authenticateLocalPlayer
else if([GKLocalPlayer localPlayer].isAuthenticated) {
//5
_enableGameCenter = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:LocalPlayerIsAuthenticated object:nil];
}
Next, switch to GameKitHelper.h and add the following to the top of the file:
extern NSString *const LocalPlayerIsAuthenticated;
With that in place switch to GameViewController.m and make the following changes:
// Add to top of file
#import "GameKitHelper.h"
// Mark the GameViewController to implement GameKitHelperDelegate
@interface GameViewController()<GameKitHelperDelegate>
@end
// Add to the implementation section
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerAuthenticated)
name:LocalPlayerIsAuthenticated object:nil];
}
- (void)playerAuthenticated {
[[GameKitHelper sharedGameKitHelper] findMatchWithMinPlayers:2 maxPlayers:2 viewController:self delegate:self];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// Add new methods to bottom of file
#pragma mark GameKitHelperDelegate
- (void)matchStarted {
NSLog(@"Match started");
}
- (void)matchEnded {
NSLog(@"Match ended");
}
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
NSLog(@"Received data");
}
The most important part here is in the playerAuthenticated
method. It calls the new method you just wrote on GameKitHelper
to find a match by presenting the matchmaker view controller.
The rest is just some stub functions when a match begins or ends that you’ll be implementing later.
That’s it! Compile and run your app, and you should see the matchmaker view controller start up:
Now run your app on a different device so you have two running at the same time (i.e. maybe your simulator and your iPhone).
Important: Make sure you are using a different Game Center account on each device, or it won’t work!
Click Play Now on both devices, and after a little bit of time, the match maker view controller should go away, and you should see something like this in your console log:
CatRace[16440:207] Ready to start match!
Congrats – you now have made a match between two devices! You’re on your way to making a networked game!