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.
Related
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.
I set up an achievement for passing the first level of my game and it works but when i replay the level and pass it it shows the notification banner again, how can i prevent this from happening?
Use this method to submit the achievement:
-(void) reportAchievementWithID:(NSString*) achievementID {
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
if(error) NSLog(#"error reporting ach");
for (GKAchievement *ach in achievements) {
if([ach.identifier isEqualToString:achievementID]) { //already submitted
return ;
}
}
GKAchievement *achievementToSend = [[GKAchievement alloc] initWithIdentifier:achievementID];
achievementToSend.percentComplete = 100;
achievementToSend.showsCompletionBanner = YES;
[achievementToSend reportAchievementWithCompletionHandler:NULL];
}];
}
Save that the user has passed the level to NSUserDefaults, then when the user passes the level check NSUserDefaults for your key, if it is there then don't do the achievement code for Game Center.
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.
Is there any method to tell the user that his score submitted successfully or just use an alert view.
I mean something like the Game Center welcome message: "Welcome Back, user"
Game Center score submission implements a block callback which you can use to handle errors or successful submission. This is a function copied directly from the developer documentation:
- (void) reportScore: (int64_t) score forCategory: (NSString*) category{
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
scoreReporter.value = score;
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil){
//There was an error submitting the score.
}else {
//The score was successfully submitted.
}
}];
}
In terms of the user interface, like the sliding "welcome back" view, you'll have to make your own user interface for that. (I just use UIAlertview, but that's totally up to you.)
I mean something like the Game Center welcome message: "Welcome Back,
user"
You can use GKNotificationBanner on iOS 5.
Have a look at the reference
I am trying to unlock an achievement in the game i am making for the iPhone but being rather unsuccessful.
From Apples own GKTapper project sample demonstrating Game Center code I have copied the GameCenterManager.h and .m and the AppSpecificValues.h files into my project. I have successfully got loading the achievements and leaderboards for viewing.
However I can't work out or get right how to actually unlock an achievement. Could some point out how using this or without the GameCenterManager how can I unlock an achievement please?
Thanks.
- (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).
}
}];
}
}
Call this method like this:
[self reportAchievementIdentifier:indentifier percentComplete:percent];
If you want to just unlock the achievement call this:
[self reportAchievementIdentifier:indentifier percentComplete:100.0];
You can use the float for calculate the percent of the achievement, and if the user reaches the 100 the achievement gets unlocked.
You can also do this:
[self reportAchievementIdentifier:indentifier percentComplete:((actualpoints/neededPoints)*100.0)];
neededPoints means the points you need for unlock this achievement. For example:
actualPoints = 300;
neededPoints = 600;
It calculates: 300/600 = 0.5 * 100 = 50%
Btw, the "completed" property isn't always set to YES if you set percentComplete=100, at least not within the same session. I spent a while debugging why my game awarded achievements several times even when percentComplete got set to 100.