So far I have had success in implementing Game Center for my app. Authorizing players is OK, so is reporting Achievements.
My issue is when I wanted to test app behavior with my iPad in flight mode.
The player will not get authorized (as I expected, so no issue) with this code.
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if ([localPlayer isAuthenticated] == YES){
NSLog(#"The local player has already authenticated.");
return;
} else {
[localPlayer authenticateWithCompletionHandler:^(NSError *error) {
if (error == nil){
NSLog(#"Successfully authenticated the local player.");
NSLog(#"Player Alias = %#", [localPlayer alias]);
} else {
NSLog(#"Failed to authenticate the player with error = %#", error);
}
}];
}
But when I later on in a UIView check if the player is authorized (so I know if I shall enable my show achievement button) with this code [achievementButton setEnabled:[localPlayer isAuthenticated]];I always get a YES as long as a user was logged in to Game Center before entering Flight Mode.
It seems like even if there is no connection to Game Center servers a previous authorized player is still seen as authorized.
This leads to that my button is shown but of course Game Center reports that it can not connect.
So, what would be the best way to check that a true connection to Game Center is available?
Cheers
I don't think there is an API to check that, but you can always make a call to one of the API that is going to return results, like getting leaderboards. It will fail if you're not connected.
Have a look at the SCNetworkReachability interfaces. Search in the docs for SCNetworkReachability, and there are other SO questions that reference this. Basically, you can use the Reachability APIs to continuously keep updated a flag that you can check to know whether or not you have a network connection.
Related
With an internet connection
Everything works flawlessly. There is no memory problem leading to crash.
With no internet connection
The app proceeds to the menu screen, where it eventually crashes because it is out of memory.
I have concluded that the problem lies in the following line of code
Social.localUser.Authenticate
When I comment out the above line, the memory problem goes away when there is no internet connection.
Here is my relevant code
void Start ()
{
Social.localUser.Authenticate(ProcessAuthentication);
}
public void ProcessAuthentication(bool success)
{
if(success)
Debug.Log ("Authenticated");
else
Debug.Log ("Failed to authenticate");
}
Leading up to the crash
2016-02-27 15:46:37.131 BrickBall[449:60670] Received memory warning.
WARNING -> applicationDidReceiveMemoryWarning()
2016-02-27 15:46:37.302 BrickBall[449:60670] Received memory warning.
WARNING -> applicationDidReceiveMemoryWarning()
2016-02-27 15:46:37.349 BrickBall[449:60670] Received memory warning.
WARNING -> applicationDidReceiveMemoryWarning()
2016-02-27 15:46:37.437 BrickBall[449:60670] Received memory warning.
WARNING -> applicationDidReceiveMemoryWarning()
Message from debugger: Terminated due to memory issue
Why would that line of code be causing the out of memory crash when there is no internet connect?
My guess is that you'll eventually need to talk to Unity. Game center will use cached credentials when there's no network connectivity to report that it successfully connected to the server and authenticated, even though it didn't. I have a bug open--and an ongoing discussion--with Apple on this. This behavior allows some game types to continue even when there's no network, then sync up later when connection is restored. However, I ran into problems where I assumed I could do things because GC said it was authenticated, but I really couldn't because it really wasn't. :/
This means apps have to handle three cases:
successful authentication with GC
failed authentication with GC
failed authentication, but reported as successful based on cached data
It's possible that Unity doesn't handle the third situation. To confirm or refute this, try the following:
Confirm that Unity does cleanly handle authentication failures
establish connectivity
log out of game center
Break connectivity (airplane mode, etc)
Retry your app
I would expect that success would be false at this point and run cleanly.
If that works as expected, I'd talk to Unity about how they handle Game Center reporting a (cached) success in a disconnected situation.
Edit2:
I had to go back and look at my code to see exactly how I hardened against it. The scenario was: while completely disconnected and/or in airplane mode, Game Center was presenting the "welcome back" message and localPlayer.authenticated was set to YES... BUT, the error code was set and complaining that it couldn't connect.
I opened bug 22232706, "[GKLocalPlayer localPlayer].authenticated always returns true after any authentication handler is set," and which still has an ongoing discussion. Apple confirmed the behavior, but says its intended.
Below is how I hardened my authentication handler to deal with this situation. It won't help you since Unity is handling this for you, but I thought other readers may find this helpful. (The TL;DR version is: always always always check the error code first, before you check .authenticated or before you check if viewController is set)
[localPlayer setAuthenticateHandler:^(UIViewController *loginViewController, NSError *error)
{
//Note: this handler fires once when you call setAuthenticated, and again when the user completes the login screen (if necessary)
//did we get an error? Could be the result of either the initial call, or the result of the login attempt
//Very important: ALWAYS check `error` before checking for a viewController or .authenticated.
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 sometimes set to YES despite the total failure. ><
//combos seen so far:
//error.code == -1009 -> authenticated = YES
//error.code == 2 -> authenticated = NO
//error.code ==3 -> authenticated = YES
if ([GKLocalPlayer localPlayer].authenticated == YES)
{
NSLog(#"error.code = %ld but localPlayer.authenticated = %d", (long)error.code, [GKLocalPlayer localPlayer].authenticated);
}
//Do stuff here to disable network play, disable buttons, warn users, etc.
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.
[rootVC2 presentViewController:loginViewController animated:NO completion:^
{
//was the login successful?
if ([GKLocalPlayer localPlayer].authenticated)
{
//enable network play, or refresh matches or whatever you need to do...
}
}];
}
//if there was not loginViewController and no error, then the user is alreay logged in
else
{
//the user is already logged in
//refresh matches, leaderboards, whatever you need to do...
}
}];
I have implemented GameKit with achievements and leaderboards in my game.
I've tested both and they seem to work.
But in order to test them correctly from the beginning (I did some tests by trials and errors) is there any way to start again completely erasing both?
I tried deleting the app form the GameCenter app of the simulation/phone but when I login again and iOS register the app in GameCenter everything re-appears.
Furthermore I implemented one achievement that can be achieved more than ones. This achievement gives 50 points. Actually I can achieve it more than ones in the game in fact I get the pop-up each time. However in the achievement list I can only see 50 points and not more, possible?
Perhaps, I didn't get the meaning of achievable more than ones..
EDIT:
I'm trying to solve this with the following method
func resetAchievements() {
// Clear all progress saved on Game Center
GKAchievement.resetAchievementsWithCompletionHandler() {(error) in
self.lastError = error
}
}
But it works only when I install the app in the device and not in the Simulator, why?
Perhaps because I don't understand the Apple's Guide
class func resetAchievementsWithCompletionHandler(_ completionHandler: ((NSError!) -> Void)!)
The following will reset all the achievements your local player has earned. You cannot earn an achievement more than once, what you are doing is posting the final value over and over again which is showing you the completion alert. The earned more than once option allows you to accept challenges from friends on that achievement. I recommend reading the introduction guide again as both of these topics are discussed in detail.
[GKAchievement resetAchievementsWithCompletionHandler: ^(NSError *error)
{
if(error == NULL)
{
NSLog(#"Achievements have been reset");
}
else
{
NSLog(#"There was an error in resetting the achievements: %#", [error localizedDescription]);
}
}];
I'm trying to get local matchmaking working in GameKit using [[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:]. Essentially, I'm trying to implement interface-less local matches: as long as there's a player in my local vicinity, I want to connect and start a match. Importantly, I only want to do this for local players: I never want to match automatically over the internet.
I've enabled Game Center for my app in iTunes connect and signed up for a different sandbox account on every device I'm using to test.
I've tried both matchmaking with GKMatchmakerViewController (after authenticating the local player) and programmatic matchmaking with startBrowsingForNearbyPlayersWithReachableHandler:, running the same code on an iPhone 4 and an 4th gen iPod Touch sitting next to each other on my desk. Neither ever finds the other; when using GKMatchmakerViewController the interface for finding nearby players remains at the
Finding Players...
spinner, and when using startBrowsingForNearbyPlayersWithReachableHandler:, the notification block never gets called.
Here's my current block of testing code:
-(void)connectLocal
{
if( ![GKLocalPlayer localPlayer].isAuthenticated )
{
// authenticateLocalPlayer calls connectLocal again after authentication is complete
[ self authenticateLocalPlayer ];
return;
}
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable) {
NSLog(#"Reachability changed for player %#", playerID );
} ];
}
The docs are a little sparse & confusing on the subject, especially when it comes to the difference between local mulitplayer and matches over the internet. For instance, it seems to be necessary to authenticate the local player and create a match before finding players to join that match (Creating Any Kind of Match Starts with a Match Request). However this little nugget seems to suggest otherwise:
The standard user interface allows players to discover other nearby players, even when neither player is currently connected to Game Center directly.
Additionally, in the flow described in Searching For Nearby Players, a match request isn't created until step 3, after finding players via the notification block passed to startBrowsingForNearbyPlayersWithReachableHandler:. Unfortunately, I've never got that far.
So, the questions:
1) Am I right in thinking I can call startBrowsingForNearbyPlayersWithReachableHandler: before authenticating the local player? GameKit doesn't throw an error, so I'm assuming it's OK. This may be a rash assumption. Whether I authenticate or not doesn't seem to make much difference.
2) Has anyone successfully implemented local auto-matching using [GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:? Is there good example code anywhere that illustrates the complete flow, from browsing for players to starting a match, all programmatically?
3) There seem to be conflicting reports on the web over whether GameKit-enabled apps can be tested in the iOS Simulator. General consensus seems not, and it's better to test on iOS hardware. I've been using a iPhone 4 & an 4th gen iPod Touch. For those who have successfully tested local multiplayer, what testing setup & methodology did you use?
1) Am I right in thinking I can call startBrowsingForNearbyPlayersWithReachableHandler: before authenticating the local player?
No. startBrowsingForNearbyPlayersWithReachableHandler works by both advertising the existing player and browsing for other players but, most importantly, the information it uses to identify players is the playerID... which won't be available until the player authenticates.
3) There seem to be conflicting reports on the web over whether GameKit-enabled apps can be tested in the iOS Simulator. General consensus seems not, and it's better to test on iOS hardware
GameCenter authentication, achievements, and leaderboards work in the simulator, everything else should be tested on real hardware. I actually recommend the simulator for authentication testing, since it avoids the sandbox/production switch which can make detailed auth testing on devices somewhat confusing. Everything else can only be tested on devices. The simulator doesn't have great support for receiving push notifications which breaks match setup and the general hardware configuration is different enough that match communication isn't unlikely to work right anyway.
You need to do these things in this order:
Authenticate the local player
Install an invite handler
Start browsing for nearby players
Authentication is required - this registers your app with Game Center and logs the player in. In most cases, you won't even need internet access to do this.
Installing the invitation handler is also required, and I think this is the step you're missing. This lets your app know what to do when an inbound invitation is received. If you don't do this, a device won't register as being nearby.
Only start browsing once you've done the above two.
Here's some sample code to get you going. Call this method after the app launches:
- (void) authenticateLocalPlayer
{
static BOOL gcAuthenticationCalled = NO;
if (!gcAuthenticationCalled) {
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
void (^authenticationHandler)(UIViewController*, NSError*) = ^(UIViewController *viewController, NSError *error) {
NSLog(#"Authenticating with Game Center.");
GKLocalPlayer *myLocalPlayer = [GKLocalPlayer localPlayer];
if (viewController != nil)
{
NSLog(#"Not authenticated - storing view controller.");
self.authenticationController = viewController;
}
else if ([myLocalPlayer isAuthenticated])
{
NSLog(#"Player is authenticated!");
//iOS8 - register as a listener
[localPlayer unregisterAllListeners];
[localPlayer registerListener:self];
[[GKLocalPlayer localPlayer] loadFriendPlayersWithCompletionHandler:^(NSArray *friendPlayers, NSError *error) {
//Do something with the friends
}];
//iOS7 - install an invitation handler
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert game-specific code here to clean up any game in progress.
if (acceptedInvite)
{
//This player accepted an invitation.
//If doing programmatic matchmaking, call GKMatchmaker's matchForInvite:completionHandler
//to get a match for the invite. Otherwise you need to allocate a GKMatchmakerViewController
//instance and present it with the invite.
}
else if (playersToInvite)
{
//Your game was launched from the GameCenter app to host a match.
}
};
//Now you can browse. Note this is the iOS8 call. The iOS7 call is slightly different.
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithHandler:^(GKPlayer *player, BOOL reachable) {
NSLog(#"Player Nearby: %#", player.playerID);
}];
}
else
{
//Authentication failed.
self.authenticationController = nil;
if (error) {
NSLog([error description], nil);
}
}
};
localPlayer.authenticateHandler = authenticationHandler;
gcAuthenticationCalled = YES;
}
}
* IMPORTANT *
If you're using iOS8, you don't install the invitation handler. You instead register an object as listening for the protocol GKLocalPlayerListener, and implement these methods:
-player:didAcceptInvite:
-player:didRequestMatchWithRecipients:
If you don't implement these methods on iOS8, it won't work!
You then link GKMatchmaker to that object by calling this after authenticating the local player:
[localPlayer registerListener:self];
Make sure the object that's implementing the protocol is declared like so in the .h file:
#interface MyImplementingObject : NSObject <GKMatchDelegate, GKLocalPlayerListener>
If you do all this and it still isn't working, make sure that you have your bundle ID set correctly in your app (Click the app, click 'Targets', make sure Bundle Identifier and Version are filled in), then click the 'Capabilities' tab (XCode 6), and make sure Game Center is on.
Go to the Member Center and make sure that the app using that bundle ID also has Game Center enabled for its Provisioning Profile. Download and reapply your Provisioning Profile if necessary.
Make sure the sandbox switch is ON in your Settings under GameCenter, and also make sure that 'Allow Invites' and 'Nearby Players' switches are turned ON.
Finally, make sure you go to iTunes Connect and verify that Game Center is enabled for your app there as well.
So, keep in mind the differences between iOS7 and iOS8. This code will work on either version and call 'updateNearbyPlayer' in turn.
if ( IS_IOS8 )
{
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithHandler:^(GKPlayer *gkPlayer, BOOL reachable)
{
NSLog(#"PLAYER ID %# is %#",gkPlayer.playerID,reachable?#"reachable" : #"not reachable");
[self updateNearbyPlayer:gkPlayer reachable:reachable];
}];
} else {
/*
* iOS 7...
*/
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable)
{
NSLog(#"PLAYER ID %# is %#",playerID,reachable?#"reachable" : #"not reachable");
[GKPlayer loadPlayersForIdentifiers:#[playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
NSLog(#"Loaded: %#, error= %#",players,error);
GKPlayer *gkPlayer = [players objectAtIndex:0];
[self updateNearbyPlayer:gkPlayer reachable:reachable];
}];
}];
}
With some delay due to the underlying Bonjour services, this mechanism works great. However, it needs to be balanced with an appropriate call to:
[[GKMatchmaker sharedMatchmaker] stopBrowsingForNearbyPlayers];
Whenever you use one of the GKPlayers/PlayerIDs reported to establish a match or to add it to an existing match, you should stop browsing. Once the match has been finished, closed or cancelled, start browsing again. Otherwise, on the second attempt to connect to a nearby device, you'll fail.
I am working on a new version of my app. I was using the sandbox normally for a while, but now all of my devices are stuck with a very strange problem. They appear to be halfway logged into game center. It doesn't work for them, but they can't log out either. Here is my authentication method:
- (void)authenticateLocalPlayer {
GKLocalPlayer* localPlayer = WJLocalPlayer;
WJLog(#"Authenticating local user...");
if (localPlayer.authenticated == NO) {
localPlayer.authenticateHandler = ^ (UIViewController* vc, NSError *error) {
if (error) {
WJLog(#"Authentication failed! %#", [error localizedDescription]);
}
else {
WJLog(#"Authentication succeeded!");
NSString* name = [GKLocalPlayer localPlayer].displayName;
WJLog(#"display name is %#", name);
NSString* alias = [GKLocalPlayer localPlayer].alias;
WJLog(#"alias is %#", alias);
GKTurnBasedEventHandler *ev = [GKTurnBasedEventHandler sharedTurnBasedEventHandler];
ev.delegate = self;
}
};
}
}
And here is what I am seeing from the log statements [WJLog is just my own version of NSLog without the garbage]:
Authenticating local user...
Authentication succeeded!
display name is Me
alias is (null)
I can log in or out in the game center app. It makes no difference. I always see the above. I even tried restoring one of the devices to factory settings. The result was still the same. I also tried disabling and re-enabling game center for the new version of my app. Still the same result.
Any ideas?
You're completely ignoring the UIViewController parameter. You're supposed to present this to the user if it exists, so they can log in. Probably you are only now experiencing this because you logged in to the non-sandbox game center, and now when you run the app it wants to ask you for your sandbox credentials, but instead you're assuming you're authenticated.
You have some other problems here too:
You should set the authenticateHandler once only, soon after your app launches.
You should check localPlayer.authenticated inside your authenticateHandler, and nowhere else, as this is the only place it's guaranteed to be valid. Specifically, it's a meaningless value after you resume from the background and until your authenticateHandler gets called again. If you need it elsewhere, use a global variable that gets initialised to false at startup and also in your applicationWillEnterForeground method, and only gets set to true inside your authenticateHandler when you've determined that the localPlayer is actually authenticated.
Check the error and log it by all means, but it doesn't tell you anything about whether authentication actually succeeded, so remove the 'else'.
Have a look at the documentation here.
I am developing an app that reports a score to Game Center using the code below (as suggested by Apple).
My problem is that even when my iPhone is in Airplane mode, the app does not trigger any score reporting error. It just goes to the "Submission ok" section of the code.
Any idea why?
Thank you!
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
scoreReporter.value = score;
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// handle the reporting error
NSLog(#"Error Descr %#",error.localizedDescription);
NSLog(#"Error Code %#",error.code);
NSLog(#"Error Domain %#",error.domain);
}
else {
NSLog(#"Submission ok");
}
}];
Starting with iOS 5.0, any network errors arising out of reportScoreWithCompletionHandler are handled internally by GameKit. This means that developers no longer have to worry about resubmitting scores pending due to network failures. If you're building with iOS 5.0 and later, the completion handler of reportScoreWithCompletionHandler will not receive any network-related errors.
I would suggest using Apple's reachability flags to detect an active connection yourself. If a connection isn't available, store your Game Center requests for future submission and submit them when network becomes available again. More on reachability can be found here