Update 2/28/14: Updated for iOS 7 and Sprite Kit.
People love to play games, whether they are casual games that are played on the bus ride home or complex ones that people spend hours on. Playing games is inherently a social activity. Players love to share their highscores, achievements and talk about the boss they defeated in the last level (I know for a fact that I do).
In this 2-part Game Center tutorial series, you’ll make a simple 2-player networked game with Sprite Kit and Game Center matchmaking.
The game you’ll be working with is very simple. It’s a racing game with a dog vs. a kid – tap as fast as you can to win!
This Game Center tutorial assumes you are already familiar with the basics of using Sprite Kit (the awesome new game engine added in iOS 7). If you are new to Sprite Kit, you might want to check out some of the other Sprite Kit tutorials on this site first.
Ready? On your mark, get set, GO!
Getting Started
This Game Center tutorial shows you how to add matchmaking and multiplayer capabilities into a simple game.
Since making the game logic itself isn’t the point of this tutorial, I’ve prepared some starter code for you that has the game without any network code.
Download the code and run the project, and you should see a screen like this:
The goal is to tap the screen as fast as you can until you reach the cat. Try it for yourself!
The game is very simple and well commented – go ahead and browse through the code and make sure you understand everything. If you get stuck on anything, check out some of our other Sprite Kit tutorials for more background information.
Enabling Game Center: Overview
At this point, you have a simple playable game, except it’s pretty boring since you’re playing all by yourself!
It would be a lot more fun to use Game Center, so you can invite friends to play with you, or use matchmaking to play with random people online.
But before you can start writing any Game Center code, you need to do two things:
- Enable Game Center for your app
- Register your app in iTunes Connect
Let’s go through each of these in turn.
Enabling Game Center for your app
Open the project in XCode, if you haven’t already and switch to the CatRaceStarter target settings. In the general tab, change the Bundle Identifier to something unique (probably based on your name or company name), like this:
It’s also a good idea to restart Xcode at this point, as it often gets confused when you switch Bundle Identifiers like this.
Then select the Capabilities tabs on the menu bar at the top.
Next, turn the switch next to the section titled Game Center on. This is a new feature introduced in Xcode 5 which makes it extremely easy to enable Game Center for your apps.
And thats it! With just the click of a button Xcode has automatically created an App ID and provisioning profile for your app, and enabled Game Center for your app – Hurray! Next, you need to register your app with iTunes Connect and enable Game Center.
Register your app in iTunes Connect
The next step is to log on to iTunes Connect and create a new entry for your app.
Once you’re logged onto iTunes Connect, select Manage Your Applications, and then click the blue Add New App button in the upper right.
On the first screen, enter a name for your app. I’ve already taken CatRace, so you’ll need to pick something else, like CatRace [Your initials]. Then enter 201 for SKU Number (or anything else you’d like), and select the bundle ID you created earlier, similar to the screenshot below:
Click Continue, and follow the prompts to set up some basic information about your app.
Don’t worry about the exact values to put in, since it doesn’t really matter and you can change any of this later – you just need to put something. To make this process simple I’ve created a zip file that contains a dummy app icon and screenshots that you can use to upload to iTunes connect.
When you’re done, click Save, and if all works well you should be in the “Prepare for Upload” stage and will see a screen like this:
Click the blue Manage Game Center button to the upper right, and select the Enable for Single Game button, and click Done. That’s it – Game Center is enabled for your app, and you’re ready to write some code!
By the way – inside the “Manage Game Center” section, you might have noticed some options to set up Leaderboards or Achievements. I won’t be covering Leaderboards and Achievements in this tutorial, but if you are interested I’ve covered this in detail in our book iOS Games by Tutorials.
Authenticate the Local Player: Strategy
When your game starts up, the first thing you need to do is authenticate the local player. Without authentication you cannot use any of the awesome features game center provides.
You can think of this as “logging the player into Game Center.” If he’s already logged in, it will say “Welcome back!” Otherwise, it will ask for the player’s username and password.
So, our strategy to authenticate the player will be as follows:
- Create a singleton object to keep all the Game Center code in one spot.
- When the app starts it will call a method on the singleton object to authenticate the local player.
- Whenever the user is authenticated (or logs out), a “authentication changed” handler will be called.
- The callback will keep track of whether the user is currently authenticated, for use later.
Now that you’re armed with this plan, let’s try it out!
Authenticate the Local User: Implementation
In the Cat Race Xcode project, right click on the CatRaceStarter group and select New Group.
Name the group GameKit. Next, right-click on the newly-created group and select New File…, choose the Objective-C class template and click Next. Name the class GameKitHelper, make it a subclass of NSObject
and click Next again. Be sure the CatRaceStarter target is checked and click Create.
Replace GameKitHelper.h with the following:
@import GameKit; @interface GameKitHelper : NSObject @property (nonatomic, readonly) UIViewController *authenticationViewController; @property (nonatomic, readonly) NSError *lastError; + (instancetype)sharedGameKitHelper; @end |
This imports the GameKit header file, and then defines two properties – one is a view controller and the other is used to keep track of the last error that occurred while interacting with Game Center APIs. You will learn more about these properties in the sections to come.
Next switch to GameKitHelper.m and add the following right inside the @implementation
section:
+ (instancetype)sharedGameKitHelper { static GameKitHelper *sharedGameKitHelper; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedGameKitHelper = [[GameKitHelper alloc] init]; }); return sharedGameKitHelper; } |
The above method is straightforward. All you are doing here is creating and returning a singleton object.
Next, while still in GameKitHelper.m, add a private instance variable to track if game center is enabled or not as shown below:
@implementation GameKitHelper { BOOL _enableGameCenter; } |
Also add the following initializer method to the implementation section. This method will simply set the above variable to true. Hence by default we assume that Game center is enabled.
- (id)init { self = [super init]; if (self) { _enableGameCenter = YES; } return self; } |
Now it’s time to add the method that will authenticate the local player. Add the following to the implementation section.
- (void)authenticateLocalPlayer { //1 GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; //2 localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) { //3 [self setLastError:error]; if(viewController != nil) { //4 [self setAuthenticationViewController:viewController]; } else if([GKLocalPlayer localPlayer].isAuthenticated) { //5 _enableGameCenter = YES; } else { //6 _enableGameCenter = NO; } }; } - (void)setAuthenticationViewController:(UIViewController *)authenticationViewController { } - (void)setLastError:(NSError *)error { } |
It seems like a lot of code, let’s go through it step-by-step to understand how the player is authenticated:
- First you get an instance of the
GKLocalPlayer
class. This instance represents the player who is currently authenticated through Game Center on this device. Only one player may be authenticated at a time. - Set the
authenticateHandler
of theGKLocalPlayer
object. GameKit may call this handler multiple times. - Store any error the callback may have received using the
setLastError:
method. - If the player has not logged into Game Center either using the Game Center app or while playing another game, the Game Kit framework will pass a view controller to the
authenticateHandler
. It is your duty as the game’s developer to present this view controller to the user when you think it’s feasible. Ideally, you should do this as soon as possible. You will store this view controller in an instance variable usingsetAuthenticationViewController:
. This is an empty method for now, but you’ll implement it in a moment. - If the player has already logged in to Game Center, then the authenticated property of the
GKLocalPlayer
object is true. When this occurs, you enable all Game Center features by setting the_enableGameCenter
boolean variable to YES. - If the user did not sign in – perhaps they pressed the Cancel button or login was unsuccessful – you need to turn off all Game Center features. This is because, Game Center features are only available if the local player has logged in.
Since the authentication process happens in the background, the game might call the player’s authenticateHandler while the user is navigating through the screens of the game or even while the player is racing.
In a situation like this, you’re going to follow this strategy: whenever the game needs to present the GameKit authentication view controller, you will raise a notification, and whichever view controller is presently onscreen will be responsible for displaying it.
First you need to define the notification name. Add this line at the top of GameKitHelper.m:
NSString *const PresentAuthenticationViewController = @"present_authentication_view_controller"; |
Now add the following code inside setAuthenticationViewController:
:
if (authenticationViewController != nil) { _authenticationViewController = authenticationViewController; [[NSNotificationCenter defaultCenter] postNotificationName:PresentAuthenticationViewController object:self]; } |
This simply stores the view controller and sends the notification.
The last method you need to fill out is setLastError:
. This method will keep track of the last error that occurred while communicating with the GameKit service. Add the following code inside setLastError:
:
- (void)setLastError:(NSError *)error { _lastError = [error copy]; if (_lastError) { NSLog(@"GameKitHelper ERROR: %@", [[_lastError userInfo] description]); } } |
This simply logs the error to the console and stores the error for safekeeping.
Next, open GameKitHelper.h and add the following extern
statement above the interface section:
extern NSString *const PresentAuthenticationViewController; |
Next, add the following method declaration to the same file.
- (void)authenticateLocalPlayer; |
You now have all the code in place to authenticate the local player. All you need to do is call the above method at the appropriate place.
Adding Game Center authentication to CatRace
Let’s pause for a moment and think about the architecture of the game. The game currently has two view controllers, one that runs the actual game and the other that shows the end result (i.e. whether the player has won or lost). To navigate between these two screens, the app uses a navigation controller.
To get a better idea about the structure, take a look at Main.storyboard:
Since navigation in the app is controller by the navigation controller you’re going to add a call to authenticate the local player there. Open GameNavigationViewController.m and replace its contents with the following:
#import "GameNavigationController.h" #import "GameKitHelper.h" @implementation GameNavigationController - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showAuthenticationViewController) name:PresentAuthenticationViewController object:nil]; [[GameKitHelper sharedGameKitHelper] authenticateLocalPlayer]; } @end |
All you’re doing here is registering for the PresentAuthenticationViewController
notification and making a call to the authenticateLocalPlayer
method of GameKitHelper
.
When the notification is received you need to present the authentication view controller returned by GameKit. To do this add the following methods to the same file.
- (void)showAuthenticationViewController { GameKitHelper *gameKitHelper = [GameKitHelper sharedGameKitHelper]; [self.topViewController presentViewController: gameKitHelper.authenticationViewController animated:YES completion:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } |
The showAuthenticationViewController
method will be invoked when the PresentAuthenticationViewController
notification is received. This method will present the authentication view controller to the user over the top view controller in the navigation stack.
Time to build and run! If you haven’t logged into Game Center before the game will present the following view:
Enter your game center credentials and press Go. The next time you launch the game, Game Center will present a banner similar to the one shown below:
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!
Where To Go From Here?
Here is a sample project with all of the code you’ve developed so far in this Game Center tutorial.
In the second part of the tutorial series, we’ll cover how to send data back and forth between each device in the game, and wrap up the game into an exciting cat vs. kid race!
In the meantime, if you have any questions or comments please feel free to add comments to the section below.
Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 1/2 is a post from: Ray Wenderlich
The post Game Center Tutorial: How To Make A Simple Multiplayer Game with Sprite Kit: Part 1/2 appeared first on Ray Wenderlich.