So (for those of you who have been living under a rock) Apple has baked a “social” gaming service for iOS into recent releases called Game Center. Game Center allows its’ users to see what games their friends are playing, as well as their own progress in the Game Center-enabled games they own.
For developers, the main advantage to Game Center is that it simplifies the creation of leaderboards and achievements for your game. Prior to Game Center, if you wanted to keep a global list of high scores, you had to write your own web service. Not only did this require a lot of extra work, but it also relied on your users creating an authenticated account with said service. Some dedicated users might create an account that they could only use with your game, but most would not. Game Center helps alleviate this problem by providing a single set of credentials that all games can use. Other services such as OpenFeint did this as well, but OpenFeint had some annoying terms of service (such as requiring their logo in your app icon). Plus, Game Center has kind of an unfair advantage, seeing as how its’ a first-party offering integrated into the OS.
An additional advantage of Game Center is that it’s dead easy to use. If a mouth breather such as myself can figure it out, then I guarantee that you can too. You create your leaderboards and achievements through a web interface in iTunes Connect; no coding required. All you have to do is wrangle the example code provided by Apple into something that works for your game. Here I’ll show you my example of how I implemented leaderboards into Revolve Ball.
I decided that I would make a “GameCenterManager” singleton class, which would then be able to be accessed from anywhere in my game. I also chose the singleton because I had pre-existing code that I could use to save/restore the object’s properties in case the app was terminated. Apple recommends that you save any scores/achievements that don’t get successfully sent to the Game Center servers, so that you can try sending them again at a later time; being able to save/restore the singleton fit right in with that recommendation.
Apple also provides a reference implementation for creating a singleton instance, but in my web searches I found a nice singleton helper which automatically creates the code necessary to turn your custom object into a singleton. I’ll leave it as an exercise for the reader to download that file and include it in your project.
Let’s begin. Take a look at the header file for my GameCenterManager object. I’ll keep the extraneous commentary to a minimum, and focus on explaining the code with inline comments.
#import <GameKit/GameKit.h>
#import "SynthesizeSingleton.h"
// Subclass our object from NSObject - allow it to be serialized, and make it the delegate for the leaderboard view
@interface GameCenterManager : NSObject <NSCoding, GKLeaderboardViewControllerDelegate>
{
// Boolean that is set to true if device supports Game Center and a player has logged in
BOOL hasGameCenter;
// An array that holds scores that couldn't be sent to Game Center (network timeout, etc.)
NSMutableArray *unsentScores;
// The view that shows the default Game Center leaderboards
UIViewController *myViewController;
}
// Create accessible properties
@property (readwrite) BOOL hasGameCenter;
@property (readwrite, retain) NSMutableArray *unsentScores;
// Time-saving singleton generator - see http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(GameCenterManager);
// These methods are all provided as examples from Apple
- (BOOL)isGameCenterAPIAvailable;
- (void)authenticateLocalPlayer;
- (void)reportScore:(int64_t)score forCategory:(NSString *)category;
- (void)showLeaderboardForCategory:(NSString *)category;
- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController;
// Serialize/store the variables in this singleton
+ (void)loadState;
+ (void)saveState;
@end
You can see that this class is pretty basic. All the class methods (except loadState and saveState) were taken from Apple’s Game Kit documentation. They include determining if Game Center is available on the device, authenticating a player, sending a high score, and showing/dismissing the leaderboard view. I added two additional properties, hasGameCenter and unsentScores. The first is a boolean that is true if the device supports Game Center and a player is logged in. Since I weak link Game Kit framework, none of the class methods actually do anything if it is set to false, and I can support the original iPad version of iOS (3.2) which doesn’t have Game Center. unsentScores holds any high score that wasn’t successfully sent to the leaderboards. The next time the player logs in to Game Center using my game, it tries to send each score again.
Now let’s look at the class implementation. Again, most of this is from Apple’s reference code. The only bits I added were checking against the hasGameCenter boolean, and the re-sending of any saved scores upon successful authentication. Make sure to read through the documentation to get a better idea of what the Game Kit classes are doing under the hood.
#import "GameCenterManager.h"
#import "cocos2d.h"
@implementation GameCenterManager
@synthesize hasGameCenter, unsentScores;
// Time-saving singleton generator - see http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
SYNTHESIZE_SINGLETON_FOR_CLASS(GameCenterManager);
- (id)init
{
if ((self = [super init]))
{
// Initialize any class properties here
if ([self isGameCenterAPIAvailable])
hasGameCenter = YES;
else
hasGameCenter = NO;
}
return self;
}
/**
Check to see if installed OS supports Game Center
*/
- (BOOL)isGameCenterAPIAvailable
{
// Check for presence of GKLocalPlayer class
BOOL localPlayerClassAvailable = (NSClassFromString(@"GKLocalPlayer")) != nil;
// Device must be running 4.1 or later
NSString *reqSysVer = @"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending);
return (localPlayerClassAvailable && osVersionSupported);
}
When the class is instantiated, the singleton runs the isGameCenterAPIAvailable method, which ensures the OS supports Game Center. If true, it sets my own hasGameCenter boolean to true. The rest of my Game Center methods check that boolean, so I can call those methods at any time and not have to worry about device support. If the device doesn’t support Game Center or the player can’t be authenticated, the leaderboard/score reporting methods just don’t do anything.
/**
Attempt to authenticate a Game Center user. Will automatically present a modal login window.
*/
- (void)authenticateLocalPlayer
{
if (hasGameCenter)
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[localPlayer authenticateWithCompletionHandler:^(NSError *error) {
if (localPlayer.isAuthenticated)
{
/* Perform additional tasks for the authenticated player here */
// If unsent scores array has length > 0, try to send saved scores
if ([unsentScores count] > 0)
{
// Create new array to help remove successfully sent scores
NSMutableArray *removedScores = [NSMutableArray array];
for (GKScore *score in unsentScores)
{
[score reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// If there's an error reporting the score (again!), leave the score in the array
}
else
{
// If success, mark score for removal
[removedScores addObject:score];
}
}];
}
// Remove successfully sent scores from stored array
[unsentScores removeObjectsInArray:removedScores];
}
}
else
{
// Disable Game Center methods - player not authenticated
hasGameCenter = NO;
}
}];
}
}
This method presents the player with a modal window to log in to Game Center. If they are already logged in, they’ll see the “Welcome back, so-and-so!” notification pop down from the top of the screen. If the player doesn’t get authenticated for some reason (i.e. they dismiss the modal), hasGameCenter is set to false, and Game Center-speicific methods are ignored for the rest of the game session.
/**
Send an integer score to Game Center for a particular category (set up category in iTunes Connect)
*/
- (void)reportScore:(int64_t)score forCategory:(NSString *)category
{
// Only execute if OS supports Game Center & player is logged in
if (hasGameCenter)
{
// Create score object
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
// Set the score value
scoreReporter.value = score;
// Try to send
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// Handle reporting error here by adding object to a serializable array, to be sent again later
[unsentScores addObject:scoreReporter];
}
}];
}
}
This is the method used to report a high score to Game Center. However, before you can use it, you’ll need to bust out of XCode and go to iTunes Connect to set up the leaderboards your game will use. The first step is to create a new app entry, with an icon, description, screenshots, etc. Go ahead and use placeholder or WIP artwork; you can change all that info later before you upload your first binary. Make sure that the “Enable Game Center” option is checked. When you’re finished, navigate to the main app page (the first one that appears after you click the app’s icon on the “Manage Your Apps” page). On the right side of the screen, there’ll be some buttons for setting up In-App Purchase, Game Center, and iAd. Click the Game Center button, then click the “Set up” button under the Leaderboards header. From there, you’ll be able to configure your leaderboard, including setting up a unique string to be used as the leaderboard ID. When you report a score to Game Center, you’ll use that string as the “category”. For example, I use the above method like this in Revolve Ball:
[[GameCenterManager sharedGameCenterManager] reportScore:bestTime forCategory:@"com.ganbarugames.revolveball.world_1"];
Next, let’s look at actually displaying the leaderboard. There are two ways you can do this: use the default Game Center-themed view controller provided by Apple, or get the data and insert it into your own UI. I’m lazy, plus UI programming takes forever, so I opted for the easier method.
/**
Show the "green felt" leaderboard view for a particular category
*/
- (void)showLeaderboardForCategory:(NSString *)category
{
// Only execute if OS supports Game Center & player is logged in
if (hasGameCenter)
{
// Create leaderboard view w/ default Game Center style
GKLeaderboardViewController *leaderboardController = [[GKLeaderboardViewController alloc] init];
// If view controller was successfully created...
if (leaderboardController != nil)
{
// Leaderboard config
leaderboardController.leaderboardDelegate = self; // The leaderboard view controller will send messages to this object
leaderboardController.category = category; // Set category here
leaderboardController.timeScope = GKLeaderboardTimeScopeAllTime; // GKLeaderboardTimeScopeToday, GKLeaderboardTimeScopeWeek, GKLeaderboardTimeScopeAllTime
// Create an additional UIViewController to attach the GKLeaderboardViewController to
myViewController = [[UIViewController alloc] init];
// Add the temporary UIViewController to the main OpenGL view
[[[CCDirector sharedDirector] openGLView] addSubview:myViewController.view];
// Tell UIViewController to present the leaderboard
[myViewController presentModalViewController:leaderboardController animated:YES];
}
}
}
/**
Since this singleton is the GKLeaderboardViewControlerDelegage, it intercepts this method and removes the view
*/
- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
[myViewController dismissModalViewControllerAnimated:YES];
[myViewController release];
}
I’m leaving out the overview for the loadState and saveState methods; an NSCoding tutorial would probably be a better place for that. However, you can download the complete GameCenterManager class and study them on your own.
The final thing I’ll mention is how to instantiate the Game Center singleton when your app starts, and then serialize its’ unsentScores array before the app quits (so data doesn’t get lost). In your app delegate file, in the applicationDidFinishLaunching method, add this line anywhere to init the object and load any saved data:
[GameCenterManager loadState];
Then, to save before your app quits, put this line in the applicationWillTerminate method in the app delegate file:
[GameCenterManager saveState];
This is just a starting spot for what you can do with Game Center. Hopefully you can take this code to the next level by adding achievement reporting and whatever new gimmicks iOS 5 has in store. Problems or suggestions? Let me know in the comments!
Was this post interesting or informative? Help support the Ganbaru Games blog by downloading my apps!





Pingback: » Game Center: Leaderboard unexpect3rd developments
Pingback: cocos2d + Game Center Achievements | Ganbaru Games
Pingback: いろいろ搭載 | おっさんフリーター(36)がアプリ作る
Pingback: Open Game Center UI with Cocos2d-x - feed99