进行 GameCenter 身份验证的正确方法是什么?

What is the proper way to do GameCenter authentication?

我在有关堆栈溢出的帖子中看到了处理 GameCenter 身份验证的片段。然而,none 这些解决方案解决了现实世界用例涵盖的任何问题。也就是说,[GKLocalPlayer localPlayer].authenticateHandler 只是状态的回调,除此之外别无其他。它提供了一个视图控制器,但在 .authenticated 和错误状态中存在大量不一致。

我正在尝试做一些事情: 1. 不弹出游戏中心登录,直到一个功能使用它 2. 尝试在应用启动时静默验证 3. 向用户提供一些信息,说明 GameCenter 功能无法正常工作的原因 4.提供恢复机制

也就是说,如果报告错误,我怎样才能显示登录对话框?

我得到这个错误没有 viewController:

案例一:

Error in GameCenterManager::authenticateLocalPlayer [The Internet connection appears to be offline.]

尽管有错误消息,设备完全在线,因为 safari 加载 cnn.com 很好。

案例二:

有人因为没有准备好而关闭了登录屏幕,在这种情况下 .authenticated 返回为 true,viewController 保持为 nil,但所有游戏中心调用都会失败。为什么 [GKLocalPlayer localPlayer].authenticated 设置为 true 而不是?

案例 3:

Error in GameCenterManager::authenticateLocalPlayer [The operation couldn’t be completed. (NSURLErrorDomain error -1009.)]

这种情况不断发生,但应用程序无法为用户做任何事情。在这种情况下,消息应该是什么?将应用程序切换到 Game Center 并在那里登录?

案例 4:

Error in GameCenterManager::authenticateLocalPlayer [The requested operation has been canceled or disabled by the user.]

如果用户取消 viewController apple 要求呈现的应用程序,就会发生这种情况。然而,也没有恢复或检测到这种状态。

案例 5:

Error in GameCenterManager::createMatch [The requested operation could not be completed because local player has not been authenticated.]

如果用户已登录,但出于某种原因退出 GameCenter,然后 returns 进入应用程序,就会发生这种情况。该应用程序将被告知用户仍然经过身份验证,但显然未通过身份验证,但我无法调用来再次登录。

所以从本质上讲,如果 GameCenter 不默默地工作,我们作为应用程序设计师该怎么办?提醒视图并告诉他们使用游戏中心应用程序登录并重新启动应用程序?

这是我的验证码:

//******************************************************
// Authenticate
//******************************************************
-(void)authenticateLocalPlayer:(bool)showLogin
{
    if( showLogin && self.loginScreen != nil )
    { [[WordlingsViewController instance] presentViewController:self.loginScreen animated:YES completion:nil]; }

    if( [GKLocalPlayer localPlayer].isAuthenticated )
    {
        NSDLog(NSDLOG_GAME_CENTER,@"GameCenterManager::authenticateLocalPlayer LocalPlayer authenticated");
    }
    __weak GameCenterManager* weakSelf = self;
    [GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController *viewController, NSError *error)
    {
        if (error != nil)
        {
            NSDLog(NSDLOG_GAME_CENTER,@"Error in GameCenterManager::authenticateLocalPlayer [%@]", [error localizedDescription]);
        }
        else
        {
            if (viewController != nil)
            {
                NSDLog(NSDLOG_GAME_CENTER,@"GameCenter: No authentication error, but we need to login");
                weakSelf.loginScreen = viewController;
            }
            else
            {
                if ( [GKLocalPlayer localPlayer].authenticated )
                {
                    NSDLog(NSDLOG_GAME_CENTER,@"GameCenter localPlayer authenticated");
                    weakSelf.gameCenterAvailable = YES;
                    weakSelf.localPlayer = [GKLocalPlayer localPlayer];
                    [weakSelf retrieveFriends];
                    [weakSelf loadPlayerPhoto:weakSelf.localPlayer];

                    for ( id<GameCenterDelegate> listener in weakSelf.listeners )
                    { [listener onPlayerAuthenticated]; }
                }
                else
                {
                    weakSelf.gameCenterAvailable = NO;
                }
            }
        }
    };
}

此函数被调用两次:一次是在应用程序启动时希望创建有效的登录状态,第二次是在用户未通过身份验证并且他们尝试使用需要游戏中心的应用程序功能时。在此应用中,它正在创建回合制比赛或查看好友

您遇到了很多关于 Game Center 的相同投诉 API。我一直在努力实现与你相同的 4 件事。 TL;DR 版本:Game Center 根本不支持它。 >< 但是您可以采取一些措施来减轻疼痛。

一件对我有帮助的一般事情:确保同时检查 NSError.underlyingError 属性。我见过几个案例,其中 NSError 太模糊而没有帮助,但潜在的错误有更具体的细节。

案例 1:能否分享 NSError 和 underlyingError 的错误域和错误代码?

案例 2:我与 Apple 有一个开放的错误,已经有很长一段时间了。有几种情况,包括在飞行模式下,身份验证失败但 .authenticated returns 为真。当我写了一个关于这个的错误时,Apple 关闭了它说这是 "by design" 所以玩家可以继续使用任何以前缓存的数据玩游戏。因此,我在错误中附加了几个缓存数据会导致严重问题的场景。我的错误被重新打开,从那以后一直坐在那里。设计理念似乎是:"well, just keep going and maybe it will all work out in the end."但最终没有成功,用户卡住了,无法玩,他们责怪我的游戏,而不是苹果。

我发现的唯一缓解措施正是您已经在做的事情:始终始终始终在身份验证处理程序中首先检查 NSError。如果设置了错误,视图控制器和 .authenticated 将完全不可靠。

如果出现错误,我会将其传递给一个专门的错误处理程序,向用户显示警报并告诉他们需要做什么才能恢复。

案例3:我也打了-1009。据我所知,当我有网络连接时会发生这种情况,但 Game Center 从未回复。这可能是由于我的路由器到游戏中心服务器没有响应之间的任何中断。我以前在使用 GC 测试服务器时经常看到这种情况。现在测试服务器已合并到生产环境中。

案例 4:你 100% 正确。没有游戏内恢复。如果用户取消身份验证,则该行结束。恢复的唯一方法是终止游戏(不仅仅是离开并重新进入)并重新启动它。然后,也只有到那时,您才能呈现另一个登录视图控制器。

不过,您可以采取一些措施来缓解这种情况。它会直接破坏您的第一个目标,即延迟登录直到需要,但我还没有找到更好的方法:

  1. 禁用 "start game" 按钮(或您游戏中的任何按钮),直到您确认登录成功且没有错误,并且您可以从 GC 成功下载示例排行榜。这证明了端到端连接。
  2. 如果用户取消登录,您的身份验证处理程序将收到域 = GKErrorDomain 和代码 = GKErrorCanceledNSError。当我看到那个组合时,我向用户发出警告,他们在成功登录之前不能玩网络游戏,并且要登录,他们必须停止并重新启动游戏。
  3. 用户对为什么 "start" 按钮被禁用感到困惑,所以我也在那里添加了一个警报。我显示了一个看起来已禁用但实际上已启用的按钮。当他们尝试点击它时,我会再次提示他们必须登录游戏中心才能玩网络游戏。

很糟糕,但至少用户没有被卡住。

案例 5:这是我在案例 2 中提到的 bug 中引用的示例之一。通过让用户认为他们已登录但实际上并未登录,他们会尝试做他们真正可以做的事情'做,最终会发生一些不好的事情。

我发现的最佳缓解措施与案例 4 相同:在您看到身份验证处理程序没有错误地触发并且您可以成功下载示例排行榜以证明之前,不要让用户启​​动会话网络连接。

事实上,通过搜索我所有的代码库,我再也没有使用 .authenticated 做任何决定。

说了这么多,这是我的身份验证处理程序。我不会说它很漂亮,但到目前为止,用户不会陷入无法恢复的境地。我想这是一个绝望的时代(用废话工作API)需要绝望的措施(笨拙的解决方法)。

[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];
     }

 }];