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,
<喎?http://www.bkjia.com/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"); }}
接下來就是提交和顯示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]; }}
附,完整PlayerModle代碼:
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
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