Game Center- handling failed achievement submissions? - iphone

Im a noob in game center # games generally. Im making my second game now and implemented the game center.
If the internet is available, there is no problem, everything works well.
But just now I purposely make the internet unreachable, and when I get an achievement, obviously it does not register to the Game Center's Achievement.
How and what's the best way to handle this issue?
Thank you....

You could add the GKAchievement objects that fail to register to an array and then resend them when you get back connectivity. You should also consider committing that array to persistent storage, in case your app terminates before it has a chance to send those achievements. Try this in your completion handler:
// Load or create array of leftover achievements
if (achievementsToReport == nil) {
achievementsToReport = [[NSKeyedUnarchiver unarchiveObjectWithFile:pathForFile(kAchievementsToReportFilename)] retain];
if (achievementsToReport == nil) {
achievementsToReport = [[NSMutableArray array] retain];
}
}
#synchronized(achievementsToReport) {
if(error == nil)
{
// Achievement reporting succeded
// Resend any leftover achievements
BOOL leftoverAchievementReported = NO;
while ([achievementsToReport count] != 0) {
[self resendAchievement:[achievementsToReport lastObject]];
[achievementsToReport removeLastObject];
leftoverAchievementReported = YES;
}
// Commit leftover achievements to persistent storage
if (leftoverAchievementReported == YES) {
[NSKeyedArchiver archiveRootObject:achievementsToReport toFile:pathForFile(kAchievementsToReportFilename)];
}
}
else
{
// Achievement reporting failed
[achievementsToReport addObject:theAchievement];
[NSKeyedArchiver archiveRootObject:achievementsToReport toFile:pathForFile(kAchievementsToReportFilename)];
}
}
Hope this helps.

I see that the two answers here focus on the specific mechanisms for archiving the undelivered achievement messages. For a higher-level description of the overall approach, you can see my answer to this related question: Robust Game Center Achievement code

Achievements (and all of the gamecenter stuff like leaderboard updates) conform to NSCoding. You can store them if you get an error submitting them and submit them later. This is what apple recommends in their docs.
Your application must handle errors when it fails to report progress to Game Center. For example, the device may not have had a network when you attempted to report the progress. The proper way for your application to handle network errors is to retain the achievement object (possibly adding it to an array). Then, your application needs to periodically attempt to report the progress until it is successfully reported. The GKAchievement class supports the NSCoding protocol to allow your application to archive an achievement object when it moves into the background.
from: http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/GameKit_Guide/Achievements/Achievements.html#//apple_ref/doc/uid/TP40008304-CH7-SW13

// Submit an achievement to the server and store if submission fails
- (void)submitAchievement:(GKAchievement *)achievement
{
if (achievement)
{
// Submit the achievement.
[achievement reportAchievementWithCompletionHandler: ^(NSError *error)
{
if (error)
{
// Store achievement to be submitted at a later time.
[self storeAchievement:achievement];
}
else
{
NSLog(#"Achievement %# Submitted..",achievement);
if ([storedAchievements objectForKey:achievement.identifier]) {
// Achievement is reported, remove from store.
[storedAchievements removeObjectForKey:achievement.identifier];
}
[self resubmitStoredAchievements];
}
}];
}
}

In case anyone stumbles upon this question in the future, Apple now has sample code for submitting achievements that includes a way to archive achievements that failed to submit (due to no network connection, etc). You'll find it in the Game Center programming guide.

Related

Allow Authenticated IOS Game Center Users to Auto-Authenticate

I am making an iphone app (a game) and while it will not be my first game, it will be my first game that uses a network connection (I intend on adding a few multiplier features)
So, now for my question.
I want the player to be able to open my app, sign into game center, and then use that to connect to my server. Is this possible? In other words:
Player signs in to game center
Sends username to my server
if (game center username doesnt exist)
{
Add their username to my sql database
}
else if (username does exist)
{
Send sign in successful message to device.
}
I believe I read another question from a while ago asking how to do this, but the answer said something about using the device UDID, which I do not want to do (for obvious reasons if you read technology related news).
So anyway, is there a trusted way that I can be sure that the user is who they say they are once they authenticate with game center that I can use to authenticate them against my server? Thank you
I think instead of UDID you can use GKPlayer.playerID which is unique to each gamecenter account. For your other purposes you can customize the completion block of authenticate handler
GKLocalPlayer * __weak localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (viewController != nil)
{
[currentViewController presentViewController:viewController animated:YES completion:nil];
}
else if (localPlayer.isAuthenticated)
{
NSLog(#"Player authenticated");
/* Your custom methods here
Sends username to my server -- send localPlayer.playerID to your server
if (game center username doesnt exist)
{
Add their username to my sql database
}
else if (username does exist)
{
Send sign in successful message to device.
}
*/
}
else
{
NSLog(#"Player not authenticated");
//disableGameCenter
}
NSLog(#"Error: %#",error);
};

Never finished [GKAchievement loadAchievementsWithCompletionHandler:]

I use the function [GKAchievement loadAchievementsWithCompletionHandler:] to restore the current player achievements in initialization. But the completionHander was never called.
- (void)loadAchievements
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil) // !!-- if a breakpoint is set here, it would never be reached
{
#synchronized(_achievementsDictionary)
{
for (GKAchievement* achievement in achievements)
[_achievementsDictionary setObject:achievement forKey:achievement.identifier];
NSLog(#"achievements loaded");
}
}
else
{
NSLog(#"Error in loading achievements: %#", error);
}
}];
}
However, a similar function, [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:] works well:
- (void) retrieveAchievmentMetadata
{
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:
^(NSArray *descriptions, NSError *error) {
if (error != nil)
{
NSLog(#"Error in loading achievement descriptions: %#", error);
}
if (descriptions != nil)
{
#synchronized(_achievementsMetaDataDictionary)
{
for (GKAchievementDescription* desc in descriptions)
{
_achievementsMetaDataDictionary[desc.identifier] = desc;
}
}
NSLog(#"achievement descriptions loaded");
}
}];
}
What might be the problem?
It comes a bit late, but maybe it helps someone else.
The fact is that GKAchievement loadAchievementsWithCompletionHandler: loads all the achievements which the local player made progress on. This means, if there are fresh achievements set up in the regarding iTunes Connect app (without any progress), they won't be loaded. Some progress has to be reported first!
On the other hand GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler: is meant to get all the information about every of the available achievements for the regarding iTunes Connect app. The description provides the identifier of the achievement, too.
For a fresh achievement the flow is the following:
Load the achievement description. (GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:)
Report some progress for the achievement to Game Center. The GKAchievement can be created based on data in GKAchievementDescription. (GKAchievementDescription reportAchievements:withCompletionHandler:)
From this point on, load the progress of the achievement to set up your app on start. (GKAchievement loadAchievementsWithCompletionHandler:)
Did you check that the returned descriptions NSArray does not have 0 elements?
if( !descriptions.count )
printf( "User has not submitted _any_ progress on _any_ achievements\n" ) ;
else for (GKAchievementDescription* desc in descriptions) ..
Note the descriptions array here only returns the collection of achievements this user has previously submitted progress on, not the array of all achievements ever registered on GameCenter for this app.

Game Center Integration for iPhone game?

Im a beginner for developing IOS applications mainly games. I have the game completed and have submitted it to the app store. In the mere future I want to submit an update which will include game center, primarily leader boards for scores (all time, monthly, weekly, and today). I'm having trouble understanding how to integrate the completed game with game-center. Another part that is unclear to me is what do I write in the code and how does gamekit framework know which number (score) to submit to game center. If anyone could provide detailed information I'd greatly appreciate it. Thanks!
here you have a sample project
http://developer.apple.com/library/ios/#samplecode/GKTapper/Introduction/Intro.html
To send the score you have this function, score is the score, category is the name of the leaderboard you configure on itunes connect.
- (void) reportScore: (int64_t) score forCategory: (NSString*) category {
GKScore *myScoreValue = [[[GKScore alloc] initWithCategory:category] autorelease];
myScoreValue.value = score;
[myScoreValue reportScoreWithCompletionHandler:^(NSError *error){
if(error != nil){
NSLog(#"Score Submission Failed");
} else {
NSLog(#"Score Submitted");
}
}];
}
You have to use this function to send the score when your player is killed, you don't have to track if it has been the highest, but you can track if it's greater than 0;
This tutorial uses the sample project functions in his own project, take a look, it includes sending points and achivements
http://maniacdev.com/2011/05/tutorial-game-center-basics-leaderboards-and-achievements/
Game Center is available since iOS SDK 4.1
1) Open the Xcode Help.
2) On the top you should see a navigation bar, which should say "Documentation" section and move your mouse to where it says "iOS 5.1 Library"(in my case).
3) Now, move your mouse over "Networking & Internet" and click on it.
4) You now should have a list of available APIs.
After that just look around for the APIs you want, like Leaderboards, and achievements.
According to your requirements you should look for things like GKLeaderboards, and anything else you are interested in. Those documentations should link to other documentations you would need. You can find the GKLeaderboards documentation on web
Edit: The game which you developed would be showing some score to the player after each instance of the Game. Post that score to the function - (void) reportScore: (int64_t) score forCategory: (NSString*) category eg. [self.gameCenterManager reportScore:yourscore forCategory: #"yourgamecategory"];
For the GameCenterManager.h and GameCenterManager.m from this link
update score to game center use this routine.
- (void) reportScore: (int64_t) score :(NSString*) YOUR_LeaderBoard_ID
{
GKScore *scoreReporter = [[GKScore alloc] initWithCategory:YOUR_LeaderBoard_ID];
scoreReporter.value = score;
scoreReporter.context = 0;
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
IsConnectFail = true;
}else{
IsConnectFail = false;
}
}];
}

How to tell when we are disconnected from GameCenter GKMatch session?

I'm wondering how do I get the disconnect message for local player when the game session is in progress and we're unable to communicate our data to other players. As there is nothing in documentation that says "this method will inform you whenever your connection fails", I'm at a bit of a loss.
I was trying to use this chunk of code in hopes that it would work, but it's futile. The "We're disconnected." message is never triggered.
- (void)match:(GKMatch *)theMatch player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (self.match != theMatch) return;
switch (state) {
case GKPlayerStateDisconnected:
//disconnected
NSLog(#"player status changed: disconnected");
matchStarted = NO;
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
if ([playerID isEqualToString:player.playerID]) {
// We have been disconnected
NSLog(#"We're disconnected.");
}
if ([delegate respondsToSelector:#selector(matchEnded)]) {
[delegate matchEnded];
}
break;
}
}
The only other line that I found might tell us that we're unable to communicate is when we actually send data like this:
- (void)sendRandomMatchData:(NSData *)data {
GKMatch *match = [GCHelper sharedInstance].match;
BOOL success = [match sendDataToAllPlayers:data
withDataMode:GKMatchSendDataReliable
error:nil];
if (!success) {
[self matchEnded];
}
}
But I assume that "success" will also be false if the opponent has disconnected and we're unable to send messages to them.
I have a pretty strict game logics, if someone has been disconnected I need to inform them that they are unable to continue playing the match and that they have lost.
Any help is highly appreciated.
There are 2 places you can do this, both are in the GKMatchDelegate protocol.
The first is implementing:
- (void)match:(GKMatch *)match player:(NSString *)playerID
didChangeState:(GKPlayerConnectionState)state
{
}
And if it's a 2 player match, the second place you can catch the disconnect is:
- (BOOL)match:(GKMatch *)theMatch shouldReinvitePlayer:(NSString *)playerID
{
}
Both those events fire reliably when the GKMatch is terminated. If Stan's answer answered your question, then I would highly recommend getting in the practice of catching NSError wherever they are available! Can save you lots of time.
So to send data and catch the error:
NSError* nsErr ;
int result = [theMatch sendData:nsd toPlayers:theMatch.playerIDs
withDataMode:GKMatchSendDataReliable error:&nsErr] ;
if( !result ) { // NO if the match was unable to queue the data.
error( nsErr, "Failed to sendRoundtripPing" ) ;
}
What about examining the error after the following code line:
BOOL success = [match sendDataToAllPlayers:data
withDataMode:GKMatchSendDataReliable
error:nil]; //replace nil with NSError variable
Maybe error will give you extra info u need.
Another idea is to create NSTimer and set some certain time for making moves/turns. If some player didn't make it for a certain time then assume this player is disconnected. Also you could check your Internet connection state to determine you have a connection cuz maybe you just lost it and that's the reason you can't send/receive any data.
Also you could check every player periodically by sending them some short amount of data using GC just to make them answer you. This way you could ensure all players are "alive" or detect some "zombie".
Remember if player moves the game to background using Home button you won't detect it anyhow cuz code in your game wont execute. And this situation doesn't mean that player is "zombie". Player could be busy by a call or something else like another app. Player could temporary loose Internet connection. This means that he/she could return to game soon...

Game Center iPhone - Load Achievement Progress

I have my app give a notification when a Game Center achievement is achieved/reaches 100%, however it shows the notification every times the user completes it, but I only want it to notify the first time in actually completed.
I found this in the Apple docs:
http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/GameKit_Guide/Achievements/Achievements.html#//apple_ref/doc/uid/TP40008304-CH7-SW11
However didn't quite understand how it help fix my issue.
I only want to call this notification once, when the achievement is first achieved. So only show it if its not already achieved.
Edit
I have this to unlock the achievement:
- (void) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
{
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
if (achievement)
{
achievement.percentComplete = percent;
[achievement reportAchievementWithCompletionHandler:^(NSError *error)
{
if (error != nil)
{
// Retain the achievement object and try again later (not shown).
}
}];
}
}
And unlock with this:
[self reportAchievementIdentifier:identifier percentComplete:percent];
Then show the notification with this line:
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
So do I simply need something like this in that code chunk?
if (achievement.completed != TRUE) {
[[GKAchievementHandler defaultHandler] notifyAchievementTitle:#"Title" andMessage:#"Message"];
}
There's a "completed"-property in GKAchievement... Try to make a new GKAchievement and check if it's not completed, then unlock it.