首页 > 代码库 > iOS - 集成game center (leader board)
iOS - 集成game center (leader board)
最近又一次用到game center里面的leader board。其实这个事情很简单,只是很容易忘记。所以就打算写下来。
iTunes Connect上创建app,然后启用game center
创建app就省略了,等创建成功后,不需要提交。我们就可以设置game center了。
首先点击新建的app,找到Game Center,如图
点击进入具体的game center设置,可以添加一些项目。很是简单,基本上都有提示,需要注意的是排行榜id,得搞个独立的,不要重复。这个id在代码里面需要使用。
就这么简单的搞几下,game center就启用了。
在代码中引入game center
在xcode的工程里面打开game center,
直接打开就行,感觉ios开发越来越傻瓜了,呵呵。
接下来就是具体的代码实现了。
代码实现
首先在合适的地方添加如下代码:通常是
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
double ver = [[UIDevice currentDevice].systemVersion doubleValue]; if (ver < 6.0) { [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) { }]; } else { [[GKLocalPlayer localPlayer] setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) { })]; } NSNotificationCenter* ns = [NSNotificationCenter defaultCenter]; [ns addObserver:self selector:@selector(authenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil];我们给game center增加了一个观察者,所以就需要在self里面提供一个函数。这是一个回调函数,如果用户没有登录game center,那么就会跑到下面,如果登陆了就会跑到上面。
- (void) authenticationChanged { if ([GKLocalPlayer localPlayer].isAuthenticated) { NSLog(@"authenticationChanged, authenticated"); } else { NSLog(@"authenticationChanged, Not authenticated"); } }
接下来就是提交和显示leader board了。具体的说明苹果的官网上说的很清楚。我这里在网上找到一个封装的源代码,自己稍微修改了一下。具体代码看最后面(附)。这里主要介绍使用流程。先增加一个属性。
@property (readwrite, retain) PlayerModel * player;再增加一个函数,如:
- (void) updatePlayer { if (!self.player || ![self.player.currentPlayerID isEqualToString:[GKLocalPlayer localPlayer].playerID]) { [self.player release]; self.player = [[PlayerModel alloc] init]; } [[self player] loadStoredScores]; }这个函数会在authenticationChanged里面被调到。
- (void) authenticationChanged { if ([GKLocalPlayer localPlayer].isAuthenticated) { NSLog(@"authenticationChanged, authenticated"); [self updatePlayer]; } else { NSLog(@"authenticationChanged, Not authenticated"); } }updatePlayer这个函数比较关键。
它支持多用户,如果是第一次登陆game center,那么就创建一个对象,如果是换了个用户登录,那么就把之前的释放,然后创建一个新的对象。然后调用loadStoredScore.
loadStoredScore会从本地文件里面读取需要传送的分数,并且往game center服务器传。
上面这段代码的意思就是app起来后,authenticationChanged被调用了,如果是登录的状态,那么就会创建一个PlayerModel对象。如果有需要上传的数据,那么就读取并且尝试上传。
其实这是个保护措施,后面会讲到为什么需要这么做。
接下来就看看如果在游戏中即时上传数据。
首先增加一个函数,这个函数就是往服务器发送数据。self.player submitScore,这个函数会在后面看到。有了这个函数,我们在游戏或者应用的某个地方可以调用往服务器发送数据了。LEADERBOARD_DISTANCE的值就是上面connect里面创建的那个排行榜id。
- (void) storeScore:(NSNumber *)distance { if (!self.player) return; int64_t score64 = [distance longLongValue]; GKScore * submitScore = [[GKScore alloc] initWithCategory:LEADERBOARD_DISTANCE]; [submitScore setValue:score64]; [self.player submitScore:submitScore]; [submitScore release]; }
ok,就这么简单。现在就大概讲讲PlayerModel的原理。因为我们在提交的时候往往会因为网络原因而失败,特别在中国。所以,PlayerModel里面就提交了一个机制,如果提交失败,就把要提交的数据保存到本地文件,在合适的时候再尝试提交。
- (void)submitScore:(GKScore *)score { if ([GKLocalPlayer localPlayer].authenticated) { if (!score.value) { // Unable to validate data. return; } // Store the scores if there is an error. [score reportScoreWithCompletionHandler:^(NSError *error){ if (!error || (![error code] && ![error domain])) { // Score submitted correctly. Resubmit others [self resubmitStoredScores]; } else { // Store score for next authentication. [self storeScore:score]; } }]; } }这个函数的主要意思就是,先尝试提交数据,如果成功,那么随便提交一下其他的数据(可能之前提交失败了)。如果失败,那么就把数据保存下来[self storeScore: score],保存到一个array,并且写入本地文件。这样就有机会在其他地方再提交一次。完整代码看后面。
现在就看看如果在app里面显示leader board。看下面的代码gameCenterAuthenticationComplete是我内部使用的一个bool,用来标记用户是否登录了game center。调用一下这个代码,就会显示iOS的game center。
- (void) showGameCenter { if (gameCenterAuthenticationComplete) { GKLeaderboardViewController * leaderboardViewController = [[GKLeaderboardViewController alloc] init]; [leaderboardViewController setCategory:LEADERBOARD_DISTANCE]; [leaderboardViewController setLeaderboardDelegate:_viewController]; [self.viewController presentModalViewController:leaderboardViewController animated:YES]; [leaderboardViewController release]; } }
header file:
#import <Foundation/Foundation.h> #import <GameKit/GameKit.h> @interface PlayerModel : NSObject { NSLock *writeLock; } @property (readonly, nonatomic) NSString* currentPlayerID; @property (readonly, nonatomic) NSString *storedScoresFilename; @property (readonly, nonatomic) NSMutableArray * storedScores; // Store score for submission at a later time. - (void)storeScore:(GKScore *)score ; // Submit stored scores and remove from stored scores array. - (void)resubmitStoredScores; // Save store on disk. - (void)writeStoredScore; // Load stored scores from disk. - (void)loadStoredScores; // Try to submit score, store on failure. - (void)submitScore:(GKScore *)score ; @end
m file:
#import "PlayerModel.h" @implementation PlayerModel @synthesize storedScores, currentPlayerID, storedScoresFilename; - (id)init { self = [super init]; if (self) { currentPlayerID = [[NSString stringWithFormat:@"%@", [GKLocalPlayer localPlayer].playerID] retain]; NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; storedScoresFilename = [[NSString alloc] initWithFormat:@"%@/%@.storedScores.plist",path, currentPlayerID]; writeLock = [[NSLock alloc] init]; } return self; } - (void)dealloc { [storedScores release]; [writeLock release]; [storedScoresFilename release]; [currentPlayerID release]; [super dealloc]; } // Attempt to resubmit the scores. - (void)resubmitStoredScores { if (storedScores) { // Keeping an index prevents new entries to be added when the network is down int index = (int)[storedScores count] - 1; while( index >= 0 ) { GKScore * score = [storedScores objectAtIndex:index]; [self submitScore:score]; [storedScores removeObjectAtIndex:index]; index--; } [self writeStoredScore]; } } // Load stored scores from disk. - (void)loadStoredScores { NSArray * unarchivedObj = [NSKeyedUnarchiver unarchiveObjectWithFile:storedScoresFilename]; if (unarchivedObj) { storedScores = [[NSMutableArray alloc] initWithArray:unarchivedObj]; [self resubmitStoredScores]; } else { storedScores = [[NSMutableArray alloc] init]; } } // Save stored scores to file. - (void)writeStoredScore { [writeLock lock]; NSData * archivedScore = [NSKeyedArchiver archivedDataWithRootObject:storedScores]; NSError * error; [archivedScore writeToFile:storedScoresFilename options:NSDataWritingFileProtectionNone error:&error]; if (error) { // Error saving file, handle accordingly } [writeLock unlock]; } // Store score for submission at a later time. - (void)storeScore:(GKScore *)score { [storedScores addObject:score]; [self writeStoredScore]; } // Attempt to submit a score. On an error store it for a later time. - (void)submitScore:(GKScore *)score { if ([GKLocalPlayer localPlayer].authenticated) { if (!score.value) { // Unable to validate data. return; } // Store the scores if there is an error. [score reportScoreWithCompletionHandler:^(NSError *error){ if (!error || (![error code] && ![error domain])) { // Score submitted correctly. Resubmit others [self resubmitStoredScores]; } else { // Store score for next authentication. [self storeScore:score]; } }]; } } @end
iOS - 集成game center (leader board)