iOS and Game Center: Tips to Avoid Sad Times

So, you’ve just written your first mediocre iPhone game. You’re getting literally tens of downloads per day. You’re raking in upwards of fifty cents per day in ad revenue. Life doesn’t get much sweeter than that.

Unfortunately, while you were reading that last paragraph, ten other developers each launched a new game and the same fickle users that downloaded your game solely because it was free are going to leave your game behind in search of other entertainment.

In an increasingly competitive App Store, it’s important to update your app semi-regularly. This serves a dual purpose: giving your users new features that provide a better experience and reminding your users that they already have an app on their phone that is worth opening.

At Palomino Labs, we’ve found that it can sometimes be difficult to find a nice bite-size chunk of functionality that makes sense as an app update. When we recently re-evaluated our game Tumble Jump in preparation for an update, we came up with several ideas, but the one that I want to focus on in this post is Game Center integration.

There are plenty of good resources that provide step-by-step instructions on how to integrate Game Center, so this post will assume that you’ve got at least a general idea of what your integration will look like. Rather than a complete tutorial, you should consider this to be a list of a few tips that will hopefully make your life a little easier if you decide to integrate Game Center.

Watch Out For Disabled Game Center

One of the more questionable features of Game Center is the watchdog that ensures that users aren’t prompted to log in too many times. The idea is that if a user has seen three login prompts without actually following through with the login process, then that user isn’t interested in Game Center and should never be shown the login prompt again. In theory, it’s not necessarily a bad idea; users will quickly become fed up with closing what feels an awful lot like a popup ad. In practice, it means that once a user has used up their three cancels, they will never again be able to get into Game Center from your app, even if they initiate the action (i.e. pressing the Game Center button on the menu screen of your game). The worst part of this is that Game Center will swallow up your request to begin authentication without so much as a peep. There is no message to tell the user that they have disabled Game Center and any time they try to interact with Game Center functionality it will look like a freeze, crash, or bug.

So how can we resolve this issue? Unfortunately, there is very little that we can do to alleviate the pain of a disabled Game Center. First, let’s look at our initial implementation to see where we might make good of a bad situation:

GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if ([localPlayer respondsToSelector:@selector(setAuthenticateHandler:)]) {
    [localPlayer setAuthenticateHandler:^(UIViewController *viewController, NSError *error) {
        if (viewController) {
            self.gameKitAuthViewController = viewController;
        }
    }];
} else {
    if ([[NSUserDefaults standardUserDefaults] boolForKey:kHasEnabledGamekitUserDefaultsKey]) {
        [localPlayer authenticateWithCompletionHandler:nil];
    }
}

Something that should jump out is the error in the completion block. We should be able to discern why authentication failed (or if it was never started) by examining that error. In fact, we do get a little bit of information from the error; unfortunately, the information is not as useful as we might hope. The message that comes to us with the error reads something like:

ErrorDomain: GKErrorDomain Code: 2 "The requested operation has been cancelled"

Well…yes, that is technically true, but the error that is returned is the exact same “error” as the case when the user cancels a login attempt. What this means is that we see the same error when a user closes the authentication dialog and when they never even see the dialog in the first place.

Faced with this issue, we decided that an extra alert was a less bad experience than the appearance of a crash, so we use the error to remind the user that Game Center might be disabled. Once the alert has been included, we ended up with an authentication handler that looks like:

GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if ([localPlayer respondsToSelector:@selector(setAuthenticateHandler:)]) {
    [localPlayer setAuthenticateHandler:^(UIViewController *viewController, NSError *error) {
        if (viewController) {
            self.gameKitAuthViewController = viewController;
        }

        if (error) {
            if (error.code == 2 && [error.domain isEqualToString:@"GKErrorDomain"]) {
                UIAlertView *alertView = [[UIAlertView alloc]
                        initWithTitle:@"Game Center"
                              message:@"If Game Center is disabled try logging in through the Game Center app"
                             delegate:self
                    cancelButtonTitle:@"OK"
                    otherButtonTitles:@"Open Game Center", nil];

                [alertView show];
            }
        }
    }];
} else {
    if ([[NSUserDefaults standardUserDefaults] boolForKey:kHasEnabledGamekitUserDefaultsKey]) {
        [localPlayer authenticateWithCompletionHandler:nil];
    }
}

Now, when a user declines to login to Game Center they will be alerted that Game Center can be disabled. Additionally, this updated handler means that when Game Center has already been disabled, the user will be given a route to fix the perceived problem if they try to take any Game Center actions. It’s worth mentioning that this isn’t a particularly elegant solution, it’s simply the least bad of the few options that Game Center has left us.

Finally, you may have noticed the second button in our alert. Since we can’t do much else to remedy Game Center’s state, we give the user the chance to go directly into the Game Center app with the following delegate method:

Use Separate Leaderboards Based on Game Conditions

One thing that Game Center is really good at communicating is rankings on a leaderboard. With essentially zero developer work, Game Center will happily tell the user exactly what ranking they have relative to other players, and it will even show what percentile the user is in. The catch is that for the information to be actually useful, users need to be compared to other users that are playing in the same conditions. We found that the definition of a “good” score changes drastically across difficulty levels and across device types. So instead of a general-purpose leaderboard, we broke our leaderboards into separate categories based on playing conditions. If your rankings are too broad to mean anything to a user, they have no reason to spend any time looking at them.

Put Some Thought Into When Achievements Should Show

It’s nice to pat the user on the back when they earn an achievement, but you should make sure that you’re not congratulating them over and over again. One example of this that we found in Tumble Jump was that we were awarding an achievement for playing the game for more than one hour. At the end of the game, the controller would add up the time that you had played so far and if it was more than one hour, it would present the achievement. The problem was that the next game that the user played, the controller would again indicate that they were over the one hour threshold and it would display the banner again.

Luckily, Apple has a setting called “Achievable More Than Once” on their achievement settings screen. So we can just turn that feature off and we won’t show achievements after the first time, right? Wrong. If you open up the info icon next to that setting, you’ll see that this setting applies to challenges, and when you turn it off, the user will continue to see achievement banners every time you post them.

What this means is that if you don’t want to annoy your users with extraneous banners, you can either switch on the GKAchievement.completed property, or you can keep your own state in something like NSUserDefaults. We decided that since we were already keeping state for aggregate-type achievements (like 1 hour total gameplay), we would decide ourselves whether or not we should update the Game Center achievements:

if ([GKLocalPlayer localPlayer].authenticated) {
    int actions = [[NSUserDefaults standardUserDefaults] integerForKey:@"actions"];
    if (actions < 5) {
        actions++;
        [[NSUserDefaults standardUserDefaults] setInteger:actions forKey:actions];
        [[NSUserDefaults standardUserDefaults] synchronize];
        GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:@"Achievement"];
        achievement.showsCompletionBanner = YES;
        achievement.percentComplete = actions * 100.0 / 5;
        [achievement reportAchievementWithCompletionHandler:nil];
    }
}

Be Wary of Overly Long Descriptions

At Palomino, one of our favorite parts of game development is writing the user-facing text. Probably no one will ever read it, but we have a lot of fun writing instructions, dialogue and App Store content that we think gives the game some of our own personality. Unfortunately, Game Center implicitly imposes a very small character maximum by truncating all text past a certain point. Apple will actually allow you to enter quite a bit of text when you are creating the description, but when you actually run the app, it’s easy for this

truncatedDescription

to turn into

inGameDescription

When you write a description, the best practice is to check out the resulting display across a couple of different device sizes. When you’re checking for truncation, you’ll need to check the completion banner and the two different types of display (detail vs list views) in Game Center.

Default Open Location For Leaderboards

When creating multiple leaderboards, Apple’s interface takes the first leaderboard that you create and calls it the “Default” leaderboard

leaderboardList

From that point on, when you present a leaderboard controller, the controller will open into that default leaderboard, regardless of what preferences the user has set (in our case, what difficulty the user was playing on). If the default leaderboard is not applicable, the user still has to go back one view before they see the list of all leaderboards. We decided that this particular behavior was not very effective for our purposes; luckily there is an easy fix for it. When presenting a leaderboard view controller, set the category to be nil. This directs the user to the root leaderboard view, where they can navigate to whichever area is most appropriate. Now, when we want to present the leaderboard, we can write:

GKLeaderboardViewController *leaderboardViewController = [GKLeaderboardViewController new];
leaderboardViewController.leaderboardDelegate = self;
leaderboardViewController.category = nil;
[[CCDirector sharedDirector] presentViewController:leaderboardViewController animated:YES completion:nil];

This will result in being taken to the list screen

downloadList

instead of the detail screen, which might not apply

downloadDetail

Posted by Ace Ellett

Ace's time at Palomino Labs has seen him immersed in iOS and aptly transitioning between server-side technologies (including Java, Ruby on Rails, and StackMob). Before joining Palomino Labs, Ace was a developer on the Boku Accounts team that built an ecosystem for consumers to use their mobile phone to make purchases at brick and mortar merchants, leading the POS integration. Prior to Boku, Ace was a developer at CRC where he led the project to modernize their 15+ year old technology stack and migrate from VB6 to C# and .NET.

About Palomino Labs

Palomino Labs unlocks the potential of software to change people and industries. Our team of experienced software developers, designers, and product strategists can help turn any idea into reality.

See the Palomino Labs website for more information, or send us an email and let's start talking about how we can work together.