处理 Game Center 身份验证

Posted

技术标签:

【中文标题】处理 Game Center 身份验证【英文标题】:Handling Game Center authentication 【发布时间】:2013-11-29 18:59:36 【问题描述】:

根据Apple docs,我们应该这样做来处理 GC 身份验证:

- (void) authenticateLocalUser

    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];

    if(localPlayer.authenticated == NO)
    
        [localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) 
            if (!error && viewcontroller)
            
                DLog(@"Need to log in");
                AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
                [appDelegate.window.rootViewController presentViewController:viewcontroller animated:YES completion:nil];

            
            else
            
                DLog(@"Success");

            
        )];

    

我们得到了这些信息:

如果设备没有经过身份验证的玩家,Game Kit 会将视图控制器传递给您的身份验证处理程序。呈现时,此视图控制器显示身份验证用户界面。你的游戏应该暂停其他需要用户交互的活动(例如你的游戏循环),呈现这个视图控制器然后返回。当玩家完成与它的交互时,视图控制器会自动关闭。

我的问题是,我们如何知道这个视图控制器何时被解除,以及我们如何知道身份验证是否成功?

显然,我需要知道身份验证是否有效,并且我需要知道如果我不得不暂停游戏,因为出现了魔法 GC 视图控制器,何时恢复游戏。

【问题讨论】:

只是想知道在 2020 年 - 这个线程仍然相关,还是有一些新功能?我发现自己必须实施一种类似的相当不优雅的解决方案来解决这个问题。似乎有关 GameKit 最新迭代的帮助非常少。 【参考方案1】:

您的代码存在问题:首先,您应该在应用加载后立即设置身份验证处理程序。这意味着无论 localPlayer 是否经过身份验证,您都可以设置处理程序,以便在玩家注销并重新登录时自动调用它。如果您的玩家从您的应用程序切换到游戏中心应用程序并注销/登录,则不会调用您应用程序中的处理程序(如果他在应用程序首次启动时已通过身份验证)。设置处理程序的目的是,每次身份验证更改(输入/输出)时,您的应用都可以做正确的事情。

其次,您不应该依赖错误来解决任何问题。即使返回错误,游戏工具包仍可能有足够的缓存信息来为您的游戏提供经过身份验证的玩家。这些错误只是为了帮助您进行调试。

要回答您的问题,请先查看下面的代码示例。

-(void)authenticateLocalPlayer

    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];

    //Block is called each time GameKit automatically authenticates
    localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
    
        [self setLastError:error];
        if (viewController)
        
            self.authenticationViewController = viewController;
            [self disableGameCenter];
        
        else if (localPlayer.isAuthenticated)
        
            [self authenticatedPlayer];
        
        else
        
            [self disableGameCenter];
        
    ;


-(void)authenticatedPlayer

     GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    [[NSNotificationCenter defaultCenter]postNotificationName:AUTHENTICATED_NOTIFICATION object:nil];
    NSLog(@"Local player:%@ authenticated into game center",localPlayer.playerID);


-(void)disableGameCenter

    //A notification so that every observer responds appropriately to disable game center features
    [[NSNotificationCenter defaultCenter]postNotificationName:UNAUTHENTICATED_NOTIFICATION object:nil];
    NSLog(@"Disabled game center");

在我的应用程序中,对authenticateLocalPlayer 的调用仅在应用程序启动时进行一次。这是因为之后会自动调用处理程序。

how do we know when this view controller gets dismissed,

你不会知道这个视图控制器什么时候被关闭。 文档中的代码示例说要在适当的时间显示视图控制器。这意味着您不必每次在游戏中心无法登录时都显示视图控制器。事实上,您可能不应该立即在处理程序中显示它。您应该仅在您的播放器需要继续手头的任务时才显示视图控制器。它不应该在一个奇怪的时间弹出。这就是我保存视图控制器的原因,以便稍后在有意义的时候显示。

如果我不得不暂停游戏,我需要知道何时恢复游戏,因为 展示了神奇的 GC 视图控制器。

如果您将身份验证处理程序设置为根据状态更改发布通知,您可以侦听该事件并显示“暂停菜单”或其他内容,直到用户选择恢复为止。

我们如何知道认证是否成功

如果认证成功,则视图控制器为 nil,localPlayer.isAuthenticated 为真。

还是不行?

如果身份验证失败,则localPlayer.isAuthenticated 为假,并且视图控制器为 nil。身份验证失败可能由于多种原因(网络等)而发生,在这种情况下您不应该显示视图控制器,这就是视图控制器将为 nil 的原因。在这种情况下,您应该禁用游戏中心功能,直到用户下一次登录。由于身份验证处理程序是自动调用的,因此大多数情况下您不需要做任何事情。如果您想提示用户在游戏中心执行某些操作,而您无法通过代码自动执行此操作,您始终可以提供一种从您的应用启动游戏中心应用的方法。

编辑:使用像 self.isAuthenticated 这样的标志(就像我在上面所做的那样)来跟踪您是否登录不是一个好主意(我不想引起任何混乱,所以我没有去掉它)。最好经常检查[GKLocalPlayer localPlayer].isAuthenticated

编辑:稍微清理一下代码 - 删除了不必要的 self.isAuthenticated 和不需要的块变量。

【讨论】:

嗨,你能解释一下这里对 blockLocalPlayer 变量的需求吗?这对我来说不是很清楚。为什么不直接使用 localPlayer var? 没有必要——正如我在一年前回答这个问题时,我只能想象在我剪切和粘贴大部分代码的项目中,我正在用块做一些事情。我已经编辑了答案。如果您查看我的代码所基于的苹果示例,您将不需要块变量:developer.apple.com/library/ios/documentation/… 是的,我仍然对积木不太满意。只是检查以确保我没有错过任何东西。谢谢。 您如何“设置您的身份验证处理程序以根据状态更改发布通知”? 实际上,您需要blockLocalPlayer,并且您需要指定它__weak。否则,编译器将警告您有关保留周期,bc localPlayer 通过块对自身具有强引用。【参考方案2】:

出于某种原因,Game Center 身份验证视图控制器是GKHostedAuthenticateViewController 的一个实例,这是一个我们不允许使用或引用的私有类。它没有给我们任何方法来干净地检测它何时被解除(与GKGameCenterViewController 的实例不同,它允许我们通过GKGameCenterControllerDelegate 协议。

此解决方案(读取解决方法)通过每四分之一秒在后台测试视图控制器何时关闭来工作。它不漂亮,但它有效。

下面的代码应该是您的presentingViewController的一部分,它应该符合GKGameCenterControllerDelegate协议。

提供了 Swift 和 Objective-C。

// Swift
func authenticateLocalUser() 
    if GKLocalPlayer.localPlayer().authenticateHandler == nil 
        GKLocalPlayer.localPlayer().authenticateHandler =  (gameCenterViewController: UIViewController?, gameCenterError: NSError?) in
            if let gameCenterError = gameCenterError 
                log.error("Game Center Error: \(gameCenterError.localizedDescription)")
            

            if let gameCenterViewControllerToPresent = gameCenterViewController 
                self.presentGameCenterController(gameCenterViewControllerToPresent)
            
            else if GKLocalPlayer.localPlayer().authenticated 
                // Enable GameKit features
                log.debug("Player already authenticated")
            
            else 
                // Disable GameKit features
                log.debug("Player not authenticated")
            
        
    
    else 
        log.debug("Authentication Handler already set")
    


func testForGameCenterDismissal() 
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) 
        if let presentedViewController = self.presentedViewController 
            log.debug("Still presenting game center login")
            self.testForGameCenterDismissal()
        
        else 
            log.debug("Done presenting, clean up")
            self.gameCenterViewControllerCleanUp()
        
    


func presentGameCenterController(viewController: UIViewController) 
    var testForGameCenterDismissalInBackground = true

    if let gameCenterViewController = viewController as? GKGameCenterViewController 
        gameCenterViewController.gameCenterDelegate = self
        testForGameCenterDismissalInBackground = false
    

    presentViewController(viewController, animated: true)  () -> Void in
        if testForGameCenterDismissalInBackground 
            self.testForGameCenterDismissal()
        
    


func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) 
    gameCenterViewControllerCleanUp()


func gameCenterViewControllerCleanUp() 
    // Do whatever needs to be done here, resume game etc

注意:log.error 和 log.debug 调用引用 XCGLogger:https://github.com/DaveWoodCom/XCGLogger

// Objective-C
- (void)authenticateLocalUser

    GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];

    __weak __typeof__(self) weakSelf = self;
    if (!localPlayer.authenticateHandler) 
        [localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError* error) 
            if (error) 
                DLog(@"Game Center Error: %@", [error localizedDescription]);
            

            if (viewcontroller) 
                [weakSelf presentGameCenterController:viewcontroller];
            
            else if ([[GKLocalPlayer localPlayer] isAuthenticated]) 
                // Enable GameKit features
                DLog(@"Player already authenticated");
            
            else 
                // Disable GameKit features
                DLog(@"Player not authenticated");
            
        )];
    
    else 
        DLog(@"Authentication Handler already set");
    


- (void)testForGameCenterDismissal

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
        if (self.presentedViewController) 
            DLog(@"Still presenting game center login");
            [self testForGameCenterDismissal];
        
        else 
            DLog(@"Done presenting, clean up");
            [self gameCenterViewControllerCleanUp];
        
    );


- (void)presentGameCenterController:(UIViewController*)viewController

    BOOL testForGameCenterDismissalInBackground = YES;
    if ([viewController isKindOfClass:[GKGameCenterViewController class]]) 
        [(GKGameCenterViewController*)viewController setGameCenterDelegate:self];
        testForGameCenterDismissalInBackground = NO;
    

    [self presentViewController:viewController animated:YES completion:^
        if (testForGameCenterDismissalInBackground) 
            [self testForGameCenterDismissal];
        
    ];


- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController*)gameCenterViewController

    [self gameCenterViewControllerCleanUp];


- (void)gameCenterViewControllerCleanUp

    // Do whatever needs to be done here, resume game etc

【讨论】:

嗨,戴夫,我有一个简短的问题,希望您无需我提供代码即可回答(否则我会发布问题并给您链接)。我的问题主要与显示游戏中心视图控制器有关。当我执行“presentGameController”时,我的游戏屏幕会显示一个 UIViewController,但它只是一个黑屏。知道为什么会发生这种情况吗? 对不起,这不是我见过的事情,所以我只是猜测。我会用一些示例代码发布一个问题。 感谢您的快速回复!我会发布一个问题让你知道【参考方案3】:

我可能错了,但我认为实际上有一种方法可以知道身份验证视图控制器何时被解除。我相信您设置的初始身份验证处理程序将在用户关闭身份验证视图控制器时被调用,但这次处理程序的 viewController 参数将为 nil。

我的应用程序的工作方式是:身份验证处理程序设置在应用程序的开头,但身份验证视图控制器仅在用户要求查看排行榜时显示。然后,当此身份验证视图控制器被解除时,初始身份验证处理程序要么显示排行榜(如果用户已通过身份验证),否则不显示。

【讨论】:

【参考方案4】:

Game Center 的 DELEGATE 方法:“gameCenterViewControllerDidFinish”在 Game Center viewController 为“Done”时自动调用。 (这是委托的强制方法。)

您可以在此方法中为您的应用添加所需的任何内容。

【讨论】:

我发现的大多数教程根本不使用这个委托。您是否有一个如何设置包含委托的游戏中心的示例? 这个委托不适用于登录视图控制器,它是GKHostedAuthenticateViewController(私有)的一个实例,与GKGameCenterControllerDelegate 协议使用的GKGameCenterViewController 实例相反。

以上是关于处理 Game Center 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

iOS 6 Game Center 在横向模式 cocos2d 中的身份验证崩溃

Game Center 沙盒中缺少邀请

再次出示 Game Center 认证

Laravel 身份验证

iOS 8 beta 5 Game Center Sandbox 无法识别我的应用

如何在 vue 上处理 Firebase 身份验证