I'm using Apples Game Center to sign the players in at the start of the game, the problem is I've just hit cancel (to test the eventually) and now the dialogue box won't appear anymore, it just keeps going straight through to disabled.
Here's the function I'm using.
-(void) setup
{
gameCenterAuthenticationComplete = NO;
if (!isGameCenterAPIAvailable()) {
// Game Center is not available.
NSLog(#"Game Center is not available.");
} else {
NSLog(#"Game Center is available.");
__weak typeof(self) weakSelf = self; // removes retain cycle error
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; // localPlayer is the public GKLocalPlayer
__weak GKLocalPlayer *weakPlayer = localPlayer; // removes retain cycle error
weakPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (viewController != nil)
{
NSLog(#"Try to show ViewController");
[weakSelf showAuthenticationDialogWhenReasonable:viewController];
}
else if (weakPlayer.isAuthenticated)
{
NSLog(#"authenticate player");
[weakSelf authenticatedPlayer:weakPlayer];
}
else
{
NSLog(#"disable");
[weakSelf disableGameCenter];
}
};
}
}
-(void)disableGameCenter
{
}
As you can see disableGameCenter doesn't actually do anything anyway.
Why is it going to disable every time I now run it? (working with the simulator) and how can I get it out of that so the the dialogue appears again? Do I have to manually force the dialogue to appear again somehow?
I had a similar issue under OSX, after you "cancel" the Gamecenter dialog a few times, it stops appearing.
To get it back, run "Game Center", Login and Logout - and then run your game again and the dialogbox will appear again ( it works at least a few times, until you 'cancel' too often ).
Related
I createed a game using this tutorial. When I call authenticateLocalPlayer like so:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) {
[self setLastError:error];
if(viewController != nil) {
[self setAuthenticationViewController:viewController];
} else if([GKLocalPlayer localPlayer].isAuthenticated) {
_enableGameCenter = YES;
} else {
_enableGameCenter = NO;
}
};
I get an error:
-[GKUnauthenticatedPlayerInternal name]: unrecognized selector sent to instance 0x14517e00
but when I add [NSThread sleepForTimeInterval:1] like this:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[NSThread sleepForTimeInterval:1];
localPlayer.authenticateHandler =
^(UIViewController *viewController, NSError *error) {
[self setLastError:error];
if(viewController != nil) {
[self setAuthenticationViewController:viewController];
} else if([GKLocalPlayer localPlayer].isAuthenticated) {
_enableGameCenter = YES;
} else {
_enableGameCenter = NO;
}
};
It starts working fine. Is this the correct way to solve the error?
I have the very same problem. And a random sleep doesn't solve it for me.
I find it strange that I can't find any other reference to this problem on the internet. We can not be the only people having this problem, can we?
I found out that what triggered the crash for me was a call to
[self performSelector: #selector(showLabel) withObject: nil afterDelay: 5],
where self is a SKSpriteNode. Note that it was not the moment when the selector was called but at the very call to performSelector, that I got the crash. Also note that the call had nothing at all to do with GKLocalPlayer or Game Center at all.
What solved the problem was to use SKAction instead, like this:
[self runAction:
[SKAction sequence: #[[SKAction waitForDuration:5],
[SKAction performSelector: #selector(showLabel) onTarget: self]]]];
I can't really explain why this does the trick. It seems to be a not too obvious bug with iOS 8.1, since this has never been a problem before.
It seems like the trick is to not call to initiate Game Center from viewDidLoad. If you do call it from there, call it after a delay. Agreed that this is broken in iOS 8.1 and 8.1.1 because it was never an issue before.
It appears that this is an issue with iOS 8.0-8.2. It appears to be resolved in iOS 8.3 in my experimentation. Strangely enough, my other game's code base seems to work consistently throughout (I haven't done exhaustive testing though).
I'm preparing to launch my first app and want to have multiple leaderboards inside my game. Currently in sandbox mode I can track and log scores into Game Center successfully. Game Center saves my scores (only if it is higher) and seems to be fully functional.
I know through Itunes Connect we have the ability to set up multiple leaderboards and it seems pretty straight forward. I still want to be able to test multiple leaderboards before publishing my game though. Is there a way to do this in sandbox mode? Currently it seems like my scores are only automatically logged into a default leaderboard. Below is the relevant code I'm using to save/access scores. Thanks!
ABGameKitHelper.m
#pragma mark - Leaderboard
-(void) reportScore:(long long)aScore forLeaderboard:(NSString*)leaderboardId
{
GKScore *score = [[GKScore alloc] initWithCategory:leaderboardId];
score.value = aScore;
[score reportScoreWithCompletionHandler:^(NSError *error) {
if (!error)
{
if(![self hasConnectivity])
{
[self cacheScore:score];
}
if (ABGAMEKITHELPER_LOGGING) NSLog(#"ABGameKitHelper: Reported score (%lli) to %# successfully.", score.value, leaderboardId);
}
else
{
[self cacheScore:score];
if (ABGAMEKITHELPER_LOGGING) NSLog(#"ABGameKitHelper: ERROR -> Reporting score (%lli) to %# failed, caching...", score.value, leaderboardId);
}
}];
}
-(void) showLeaderboard:(NSString*)leaderboardId
{
GKLeaderboardViewController *viewController = [GKLeaderboardViewController new];
viewController.leaderboardDelegate = self;
if (leaderboardId)
{
viewController.category = leaderboardId;
CCLOG(#"Going to category already created");
}
[[self topViewController] presentViewController:viewController animated:YES completion:nil];
}
MainScene.m
- (void)gameCenter {
[[ABGameKitHelper sharedHelper] reportScore:1400 forLeaderboard:#"Score"];
[[ABGameKitHelper sharedHelper] showLeaderboard:#"Score"];
}
I'm not sure if I understand your question properly, but I'll try to answer! Game Center does support multiple leaderboards:
-If you want to send a score to specific leaderboard, you just have to call the function [[ABGameKitHelper sharedHelper] reportScore:X forLeaderboard:LEADERBOARD_ID];, where X represents the score you'd like to send, and LEADERBOARD_ID is the ID of the leaderboard you want to send the score to, as specified in iTunes Connect.
-When you have multiple leaderboards, if you don't want to show just one leaderboard, but a list of them all, you should use the GKGameCenterViewController class instead. However, be careful; this ViewController has been added in iOS 6 only, so you must check which version the device is running. I am also using the ABGameKitHelper, so I've made a function to show this kind of view. Here it goes :
ABGameKitHelper.m
- (void) showGameCenter{
if (![[ABGameKitHelper sharedHelper] hasConnectivity]) return;
//Check if device runs on iOS 5
if([[[UIDevice currentDevice]systemVersion]intValue]==5)
{
//If so, we must use the GKLeaderboardViewController
GKLeaderboardViewController *leaderboard = [[GKLeaderboardViewController alloc] init];
if (leaderboard != nil)
{
leaderboard.leaderboardDelegate = self;
[[self topViewController] presentViewController:leaderboard animated:YES completion:nil];
}
}else if ([[[UIDevice currentDevice]systemVersion]intValue]>=6)
{
//if it runs on iOS 6 or higher, we use GKGameCenterViewController
GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init];
if (gameCenterController != nil)
{
gameCenterController.gameCenterDelegate = self;
gameCenterController.viewState = GKGameCenterViewControllerStateDefault;
[[self topViewController] presentViewController:gameCenterController animated:YES completion:nil];
}
}
}
And don't forget to add :
- (void) gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
Using this function will allow you to show a nice view containing all your leaderboards and achievements.
Hope this helps!
According to the Apple docs we should do something like this to handle GC authentication:
- (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");
}
})];
}
}
And we are given this information:
If the device does not have an authenticated player, Game Kit passes a view controller to your authenticate handler. When presented, this view controller displays the authentication user interface. Your game should pause other activities that require user interaction (such as your game loop), present this view controller and then return. When the player finishes interacting with it, the view controller is dismissed automatically.
My question is, how do we know when this view controller gets dismissed, and how do we know if the authentication succeeded or not?
Obviously I need to know if the authentication worked or not, and I need to know when to resume the game if I had to pause it because the magic GC view controller was presented.
There is a problem with your code: First and foremost, you should set the authentication handler as soon as your app loads. This means that regardless of whether the localPlayer is authenticated or not, you set the handler so that it is automatically called if the player is logged out and logged back in again. If your player switches from your app to the game center app, and logs out / in, then the handler in your app won't be called (if he was already authenticated when the app first started up). The point of setting the handler is so that every time there is an auth change (in / out), your app can do the right thing.
Secondly, you shouldn't be relying on the error for anything. Even if an error is returned, game kit may still have enough cached information to provide an authenticated player to your game. The errors are only to assist you with debugging.
To answer your questions, first review my code example below.
-(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");
}
In my app, the call to authenticateLocalPlayer is made only once, when the app is launched. This is because the handler is invoked automatically after that.
how do we know when this view controller gets dismissed,
You won't know when this view controller gets dismissed.
The code example in the documentation says to show the view controller at the appropriate time. This means that you shouldn't necessarily show the view controller every time that game center isn't able to log in. In fact, you probably shouldn't present it immediately in the handler. You should show the view controller only when it is necessary for your player to proceed with the task at hand. It shouldn't pop up at a weird time. That is why I save the view controller, so I can display later when it makes sense to.
I need to know when to resume the game if I had to pause it because
the magic GC view controller was presented.
If you setup your authentication handler to post notifications based on status changes, you can listen for the event and show a "pause menu" or something, that remains until the user chooses to resume.
how do we know if the authentication succeeded
If the authentication succeeded, then the view controller is nil, and localPlayer.isAuthenticated is true.
or not ?
If authentication failed, then localPlayer.isAuthenticated is false, and the view controller was nil. Authentication failing could have happened for a number of reasons (network etc), and you shouldn't be presenting the view controller in this case, which is why the view controller will be nil.In this scenario, you should disable game center features until the user is next logged in. Since the authentication handler is called automatically, most of the time you shouldn't need to do anything. You can always provide a means to launch the game center app from your app, if you want to prompt the user to do something in game center, which you can't do automatically through your code.
EDIT: using a flag like self.isAuthenticated (as I did above)to keep track of whether you are logged in or not is not a great idea (I didn't want to cause any confusion, so I didn't remove it). It is better to always check [GKLocalPlayer localPlayer].isAuthenticated
EDIT: Cleaned up code a bit - removed unnecessary self.isAuthenticated, and block variable which isn't required.
For some reason, the Game Center authentication view controller is an instance of GKHostedAuthenticateViewController which is a private class we're not allowed to use or reference. It doesn't give us any way to cleanly detect when it is dismissed (unlike instances of GKGameCenterViewController which allow us to via the GKGameCenterControllerDelegate protocol.
This solution (read workaround) works by testing in the background every quarter of a second for when the view controller has been dismissed. It's not pretty, but it works.
The code below should be part of your presentingViewController, which should conform to the GKGameCenterControllerDelegate protocol.
Swift and Objective-C provided.
// 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
}
Note: the log.error and log.debug calls are referencing 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
}
I might be wrong, but I think there actually is a way to know when the authentication view controller gets dismissed. I believe the initial authenticate handler that you set will be called when the user dismisses the authentication view controller, except this time the viewController parameter of the handler will be nil.
The way my app works is: the authenticate handler is set at the beginning of the application, but the authentication view controller is only displayed when the user asks to view the Leaderboards. Then, when this authentication view controller is dismissed, the initial authenticate handler either displays the leaderboards if the user was authenticated or doesn't if he wasn't.
The Game Center DELEGATE method: 'gameCenterViewControllerDidFinish' is called automatically when the Game Center viewController is 'Done'. (This is a compulsory method for the delegate.)
You can put whatever you need for your app, in this method.
How can I use Game Center or the GameKit Framework with a Sprite Kit Xcode template?
In Sprite kit, it uses Scenes; but normally to view the leaderboards for example you need to "presentModalViewController" but that is not possible in SKView.
And how can I authenticate the player and all that other fun stuff in iOS 6.
Thanks in advance!
You can use "presentModalViewController" by using this code to access the root view controller
UIViewController *vc = self.view.window.rootViewController;
[vc presentViewController: gameCenterController animated: YES completion:nil];
Now you can access your ModelViewController anywhere include in SKScenes. I did it in my newest game and it worked well
Besides, I suggest you use the separate object to control game center like leaderboard and achievement so you can reuse it in your next game.
Here is an updated authenticate local player, but Ravindra's code also works.
- (void) authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (viewController != nil)
{
//showAuthenticationDialogWhenReasonable: is an example method name. Create your own method that displays an authentication view when appropriate for your app.
//[self showAuthenticationDialogWhenReasonable: viewController];
}
else if (localPlayer.isAuthenticated)
{
//authenticatedPlayer: is an example method name. Create your own method that is called after the loacal player is authenticated.
//[self authenticatedPlayer: localPlayer];
}
else
{
//[self disableGameCenter];
}
};
}
Swift 2.0
func authenticateLocalPlayer() {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = { (viewController, error ) -> Void in
if (viewController != nil) {
let vc:UIViewController = self.view!.window!.rootViewController!
vc.presentViewController(viewController!, animated: true, completion:nil)
} else {
print ("Authentication is \(GKLocalPlayer.localPlayer().authenticated) ")
GlobalData.loggedIntoGC = true
// do something based on the player being logged in.
GlobalData Swift File:
static var loggedIntoGC:Bool = false
Call Method in your scene where Game Center is being enabled:
ie HUD or GameScene in the
override func didMoveToView(view: SKView)`
authenticateLocalPlayer()
you can authenticate like this
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
if (error == nil)
{
static_setEnable( true );
NSLog(#" Authenticate local player complete");
}
else
{
static_setEnable( false );
NSLog(#"Authenticate local player Error: %#", [error description]);
}
}];
}
I have a problem with the integration of game center in my app' which use iOS 6 SDK.
In fact I use the sample code from Apple, but it looks like incomplete :
I have tried this code :
-(void) authenticateLocalPlayer {
GKLocalPlayer* localPlayer =
[GKLocalPlayer localPlayer];
localPlayer.authenticateHandler =
^(UIViewController *loginVC,
NSError *error) {
[self setLastError:error];
if ([GKLocalPlayer localPlayer].authenticated)
{
// authentication successful
[self enableGameCenterForPlayer:[GKLocalPlayer localPlayer]];
}
else if (loginVC)
{
// player not logged in yet, present the vc
[self pauseGame];
[self presentLoginVC:loginVC];
}
else
{
// authentication failed, provide graceful fallback
[self disableGameCenter];
}
};
}
But the problem is that enableGameCenterForPlayer, pauseGame, presentLoginVC, disableGameCenter are not implemented methods, and it returns :
Instance method '-enableGameCenterForPlayer:' not found (return type defaults to 'id')
How can I fix this problem ?
Thanks
I use the method [self presentLoginVC:VC] to pass my UITabViewController or UINavigationController the viewController because the block below is not on the main thread.
localPlayer.authenticateHandler = ^(UIViewController *loginVC, NSError *error) {
When you are in a block you should be sure not to change UI elements because you really don't know when it will complete or where you will be in your app. There are probably many ways to do this, but this is my solution.
Below is my UITabBarController 'category' .m file (additions of methods for a class without subclassing) I create the method presentLoginVC and just have it call 'showGameCenterViewController' through my UITabBarController:
#import "UITabBarController+GameKitAdditions.h"
#implementation UITabBarController (GameKitAdditions)
-(void) showGameCenterViewController: (UIViewController *)VC {
[self presentViewController:VC animated:NO completion:nil];
}
-(void)dismissGameCenterViewController:(UIViewController *)VC {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
As to the other functions:
-(void) enableGameCenterForPlayer:(GKLocalPlayer *) localPlayer;
-(void) disableGameCenter;
-(void) pauseGame;
They could be as simple as just setting a BOOL called enableGameCenter to YES or NO. To get around errors you can just add these prototypes to your .h file and then write the functions just to output something to NSLog() or something.