最近又一次用到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,

<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+1rG907Tyv6q+zdDQo6y40L71aW9zv6q3otS9wLTUvcm1uc/By6Osuse6x6GjPC9wPgo8cD6908/CwLS+zcrHvt/M5bXEtPrC68q1z9bBy6GjPC9wPgo8cD48YnI+CjwvcD4KPHA+PHN0cm9uZz60+sLryrXP1jwvc3Ryb25nPjwvcD4KPHA+ytfPyNTaus/KyrXEtdi3vcztvNPI58/CtPrC66O6zaizo8rHPC9wPgo8cD4tIChCT09MKWFwcGxpY2F0aW9uOihVSUFwcGxpY2F0aW9uICopYXBwbGljYXRpb24gZGlkRmluaXNoTGF1bmNoaW5nV2l0aE9wdGlvbnM6KE5TRGljdGlvbmFyeQogKilsYXVuY2hPcHRpb25zPC9wPgo8cD48cHJlIGNsYXNzPQ=="brush:java;">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");
}
}@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];
}- (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#import @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
#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