What is the proper way to do GameCenter authentication?

前端 未结 1 1388
余生分开走
余生分开走 2021-01-20 18:34

I have seen in posts around stack overflow that shows snippets of handling GameCenter authentication. However, none of these solutions address any of the problems that real

相关标签:
1条回答
  • 2021-01-20 19:04

    You're encountering many of the same complaints I have about the Game Center API. I've been trying to achieve the same 4 things you are. The TL;DR version: Game Center simply doesn't support it. >< But there are some things you can do to reduce the pain.

    One general thing that helped me: make sure to check both the NSError as well as it's .underlyingError property. I've seen several cases where the NSError is too vague to be helpful, but the underlying error has more specific details.

    Case 1: Can you share the error domain and error code for the both the NSError and the underlyingError?

    Case 2: I have an open bug with Apple on this, for a looooong time. There are several cases, including being in Airplane mode, where the authentication fails but .authenticated returns true. When I wrote a bug on this, Apple closed it saying this was "by design" so players could continue to play the game using any previously cached data. So, I appended the bug with several scenarios where cached data causes significant problems. My bug was re-opened and has sat there ever since. The design philosophy seems to be: "well, just keep going and maybe it will all work out in the end." But it doesn't work out in the end, the user gets stuck, unable to play and they blame my game, not Apple.

    The only mitigation I have found is exactly what you're already doing: Always always always check the NSError first in the authentication handler. The view controller and .authenticated are totally unreliable if an error has been set.

    If there is an error, I pass it to one dedicated error handler that displays alerts to users and tells them what they need to do to recover.

    Case 3: I have hit -1009 as well. From what I can discern it happens when I have network connection, but Game Center never replied. That could come from any disruption anywhere between my router up-to-and-including Game Center servers not responding. I used to see this a lot when using the GC Test Servers. Not so much now that the test servers were merged into the prod environment.

    Case 4: You are 100% correct. there is no in-game recovery. If the user cancels the authentication, that's the end of the line. The only way to recover is to kill the game (not just leave and re-enter) and restart it. Then, and only then, you can present another login view controller.

    There are some things you can do to mitigate this, though. It will directly break your #1 goal of delaying login until needed, but I haven't found anything better:

    1. Disable the "start game" button (or whatever you have in your game) until you've confirmed the login succeeded with no errors AND you can successfully download a sample leaderboard from GC. This proves end-to-end connectivity.
    2. If the user cancels the login, your authentication handler will receive an NSError of domain = GKErrorDomain and code = GKErrorCanceled. WHen I see that combo, I put up a warning to the user that they cannot play network games until they've successfully logged in and to login they will have to stop and restart the game.
    3. Users were confused why the "start" button was disabled, so I added an alert there too. I show a button that looks disabled but is really enabled. And when they try to click it, I again present an alert telling them they have to login in to game center to play a network game.

    It sucks, but at least the user isn't stuck.

    Case 5: This is one of the examples I cited in my bug referred to in case 2. By letting the user think they're logged in when they really aren't, they try to do things they really can't do, and eventually something bad will happen.

    The best mitigation I have found for this is the same as Case 4: don't let the user start a session until you see the authentication handler fire with no errors AND you can successfully download a sample leaderboard to prove the network connection.

    In fact, doing a search through all of my code bases, I never use .authenticated for any decisions anymore.

    Having said all of that, here's my authentication handler. I won't say it's pretty, but thus far, users don't get stuck in unrecoverable situations. I guess it's a case of desperate times (working with a crap API) requires desperate measures (kludgy work arounds).

    [localPlayer setAuthenticateHandler:^(UIViewController *loginViewController, NSError *error)
     {
        //this handler is called once when you call setAuthenticated, and again when the user completes the login screen (if necessary)
         VLOGS (LOWLOG, SYMBOL_FUNC_START, @"setAuthenticateHandler completion handler");
    
         //did we get an error? Could be the result of either the initial call, or the result of the login attempt
         if (error)
         {
             //Here's a fun fact... even if you're in airplane mode and can't communicate to the server,
             //when this call back fires with an error code, localPlayer.authenticated is set to YES despite the total failure. ><
             //error.code == -1009 -> authenticated = YES
             //error.code == 2 -> authenticated = NO
             //error.code == 3 -> authenticated = YES
    
             if ([GKLocalPlayer localPlayer].authenticated == YES)
             {
                 //Game center blatantly lies!
                 VLOGS(LOWLOG, SYMBOL_ERROR, @"error.code = %ld but localPlayer.authenticated = %d", (long)error.code, [GKLocalPlayer localPlayer].authenticated);
             }
    
             //show the user an appropriate alert
             [self processError:error file:__FILE__ func:__func__ line:__LINE__];
    
             //disable the start button, if it's not already disabled
             [[NSNotificationCenter defaultCenter] postNotificationName:EVENT_ENABLEBUTTONS_NONETWORK object:self ];
             return;
         }
    
         //if we received a loginViewContoller, then the user needs to log in.
         if (loginViewController)
         {
             //the user isn't logged in, so show the login screen.
             [appDelegate presentViewController:loginViewController animated:NO completion:^
              {
                  VLOGS(LOWLOG, SYMBOL_FUNC_START, @"presentViewController completion handler");
    
                  //was the login successful?
                  if ([GKLocalPlayer localPlayer].authenticated)
                  {
                      //Possibly. Can't trust .authenticated alone. Let's validate that the player actually has some meaningful data in it, instead.
                      NSString *alias = [GKLocalPlayer localPlayer].alias;
                      NSString *name = [GKLocalPlayer localPlayer].displayName;
                      if (alias && name)
                      {
                          //Load our matches from the server. If this succeeds, it will enable the network game button
                          [gameKitHelper loadMatches];
                      }
                  }
              }];
         }
    
         //if there was not loginViewController and no error, then the user is already logged in
         else
         {
             //the user is already logged in, so load matches and enable the network game button
             [gameKitHelper loadMatches];
         }
    
     }];
    
    0 讨论(0)
提交回复
热议问题