Saturday, April 30, 2011

Game Center invitation errors. Two things often overlooked when dealing with GKInvite.

The past few days I've been working on integrating Snooker Club with most of Game Kit's functionality (leaderboards, achievements, multiplayer, voice chat). Having done this before it went like a breeze. The multiplayer capabilities add a lot to the fun and playability of the game... if they work. In certain situations the game wouldn't connect with other players, it wouldn't even acknowledge that an invite had been received. It didn't take long to sort it out, but perhaps I can save you a little bit of time by outlining the 2 most common scenarios that are overlooked in Apple's Game Kit guide.


Scenario 1


Inviting a friend works fine when you're testing? The app is off, you get an invite, the app launches and manages to connect. Funnily enough, when testing on a larger user base it would seem that when friends in close proximity want to challenge each other they tend to wait for their invites whilst GKMatchmakerViewController is already being displayed. In the example Apple provides your code for handling an invite should look like this:


[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
   // Insert application-specific code here to clean up any games in progress.
   if (acceptedInvite)
    {
        GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
        mmvc.matchmakerDelegate = self;
        [self presentModalViewController:mmvc animated:YES];
    }
    else if (playersToInvite)
    {
        GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
        request.minPlayers = 2;
        request.maxPlayers = 4;
        request.playersToInvite = playersToInvite;
 
        GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
        mmvc.matchmakerDelegate = self;
        [self presentModalViewController:mmvc animated:YES];
    }
};

The code above doesn't take into account that you might already be displaying an instance of GKMatchmakerViewController as a modal view. Trying to display a new instance of GKMatchmakerViewController initialized with the new invite and displaying it as a modal view controller leads to nothing. In order to handle this particular scenario you need to dismiss the visible modal view controller before presenting the new one, like so:


[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
    UIViewController *topLevelViewController = (UIViewController*)[[(GameHostAppDelegate*)[UIApplication sharedApplication] delegate] gameHostViewController];
    // Insert application-specific code here to clean up any games in progress.

    if (acceptedInvite)
    {
     isInviter = NO;
     GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
     mmvc.matchmakerDelegate = self;
     
     if([topLevelViewController modalViewController] != nil)
      [topLevelViewController dismissModalViewControllerAnimated:NO];

     [topLevelViewController presentModalViewController:mmvc animated:YES];
    }
    else if (playersToInvite)
    {
     isInviter = NO;
     GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
     request.minPlayers = 2;
     request.maxPlayers = 2;
     request.playersToInvite = playersToInvite;
     
     GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
     mmvc.matchmakerDelegate = self;
     
     if([topLevelViewController modalViewController] != nil)
      [topLevelViewController dismissModalViewControllerAnimated:NO];

     [topLevelViewController presentModalViewController:mmvc animated:YES];
    }



Make sure you don't animate the view controller being dismissed, otherwise the new instance of GKMatchmakerViewController will not show up.


Scenario 2


You've applied the fix for the issue mentioned above. Things work much better, but still feel a little unreliable? Try this.

  • Launch your app.
  • Put your app into the background.
  • Send an invite to yourself from another device.
  • Accept the invite.

If the invite is handled correctly go through the steps again, this time on a slower connection. Did you notice how unreliable your invitation handler has become? Apple's documentation states the following: Important:Your application should provide an invitation handler as early as possible after your application authenticates the local player; an appropriate place to set the handler is in the completion handler that executes after the local player is authenticated. It is critical that your application authenticate the local player and set the invitation handler as soon as possible after launching, specifically so that you can handle the invitation that caused your application to be launched.

There is a very important thing to extract from Apples info. Your application should provide an invitation handler as early as possible after your application authenticates the local player. You don't want to have an invitation handler in place if your user is not authenticated. This becomes a bit tricky with multitasking.

If you're on a device that supports multitasking you need to authenticate every time your app enters the foreground (as of iOS 4.2 this is handled automatically). You're probably already doing something like:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
 [gameCenter validateAuthentication];
}

where validateAuthentication checks to see if you're still logged in as the same player, that you're authenticated and if not tries to authenticate you.

This leads to an interesting situation, which results in unreliable handling of invites. Look at the 4 steps above. When you accept an invitation, your app goes into the foreground. The [GKMatchmaker sharedMatchmaker].inviteHandler is called but the player has most probably not been authenticated yet. Nothing happens. Whats the solution, you ask? Simple.

In your application's delegate method - (void)applicationDidEnterBackground:(UIApplication *)application simply add this line:

[GKMatchmaker sharedMatchmaker].inviteHandler = nil;

This way the invite handler won't be called until the player is authenticated. Once the player is authenticated the inviteHandler will be created once again, assuming you've followed Apple's guides and added the code to create the handler after the user authenticates ( in [localPlayer authenticateWithCompletionHandler:^(NSError *error) ).

I hope that the solutions to these 2 common scenarios bring you closer to solving reliability issues with Game Center.

No comments:

Post a Comment