你也許曾聽說過Game Center,它是自打iOS 4.1被引入的線上多人社交遊戲網絡,支援玩家邀請好友一起玩兒遊戲,還可以建立一個多人遊戲的會話,追蹤成就系統,以及其他功能。
除了可以讓開發者更輕松的實作一些基本功能外,它還改善了另一個基礎問題:app推廣。如今App Store上有超過1百萬款app,單個使用者發現你的app的機率将會非常低。Game Center通過好友系統改善了此類問題,你可以檢視你的好友都在玩兒些什麼遊戲,是以你的遊戲的曝光率被增加了。
iOS 6.0為Game Center引入了一系列新的API,它們不僅可以增加你的遊戲曝光率,而且還能增加使用者粘性。其中一項功能是挑戰好友,即使你的Gamecenter好 友們沒有安裝此遊戲,也可以邀請向他們發送挑戰。例如,一個玩家在你的遊戲裡得到了高分,他可以向他的朋友發送一個挑戰邀請并且說:“嘿,來試試赢我 啊!”
當朋友接收到挑戰後,會立即看到消息中你的遊戲的連結。不難想象這一特性能夠成倍的增加使用者留存率!由于考慮到Game Center上龐大的使用者群體,這一點足夠說明你應該在遊戲中添加挑戰功能。
使用挑戰之前要先使用Game Center,是以本篇教程将首先帶你整體過一遍Game Center,包括設定Game Center并添加一個簡單的排行榜,同時會在過程中指出iOS 6新增的内容。
注意: 本篇教程要求你熟悉Cocos2D并且基于它制作過遊戲。如果你是Cocos2D的新手,可以先在本網站學習Cocos2D系列教程。
跳躍猴遊戲 首先下載下傳初始工程 – 跳躍猴!
MonkeyJump(跳躍猴)是一個簡單的橫向卷軸遊戲,它由我最喜歡的遊戲引擎Cocos2D制作的。它是基于一個由Cocos2D學習工作室制作的叫做CatJump的遊戲。我向其中加了一些有趣的元素,另外Vicki Wenderlich為遊戲制作了新的美術資源。
遊戲中的主要角色很明顯,是一隻猴子,遊戲的主要目标讓猴子達到最遠的距離同時躲避敵人。
MonkeyJump非常容易上手,即使是你的媽媽也能玩兒!隻需要輕點螢幕就可以讓猴子跳過敵人了。遊戲會記錄猴子跑的距離并以此為玩家打分。
iOS GameCenter 挑戰,排名 親自是玩兒一下吧!把starter項目解壓,在Xcode中編譯并運作。試試看你能跑多遠!:]
另外簡單過一遍代碼,看看這些層和場景之間是如何協調工作的。
配置Game Center 在做任何有關Game Center Challenges功能之前,首先要做的就是配置你的app使用Game Center!這個過程需要三個步驟:
建立并設定App ID。 在iTunes Coonect上注冊你的app。 啟用Game Center的功能,比如leaderboards(排行榜) 讓我們按順序過一遍這些步驟。對于許多有Game Center經驗的讀者來說,這會是相當熟悉的,但我保證我會很快的講完這一部分。
建立并設定APP ID 第一步,你需要建立一個App ID。登入到iOS Dev Center,選擇iOS Provisioning Portal。
在Provisioning Portal中,選擇App IDs,選擇create a new App ID。使用monkeyjump作為遊戲名字并輸入一個bundle identifier,通常這裡使用倒轉的DNS命名,比如com.ali.MonkeyJump(如果你沒有自己的域名,你可以使用你自己的名字代 替)。
iOS GameCenter 挑戰,排名 當你完成後,點選Submit按鈕。打開MonkeyJump Xcode工程,選擇project root,然後選擇MonkeyJump target,在Summary tab中把Bundle Identifier替換為你剛剛在Provisioning Portal中建立的那個。
iOS GameCenter 挑戰,排名 編譯并運作,在真機上運作試試看。如果一切配置都正确的話,遊戲應該立刻啟動。如果沒有,那麼clean一下項目并重新編譯一次。
在ITUNES CONNECT中注冊你的APP 接下來的步驟是iTunes Connect中建立一個新app。首先登入到iTunes Connect,切換到application management子頁面,點選位于左上角的Add New App按鈕。(如果你同時擁有Mac和iOS的開發者帳号,你可能需要選擇app的類型 – 當然要選擇iOS)。
在第一個螢幕中,輸入MonkeyJump作為遊戲名字,400作為SKU number(SKU number可以是任意的數字/單詞,你也可以設定成你想要的)并選擇上一步中建立的Bundle Identifier。
iOS GameCenter 挑戰,排名 當你輸入完所有值後,點選Continue。在彈出的提示框中輸入所有需要的資訊。因為你隻需要在本教程中使用此項目,是以一切從簡,隻填入強制要求填入的項目。☺
你需要上傳一個大的app icon 和一個截圖。為了讓過程更容易,我為你準備好了iTunes resources file。你可以解壓這個ZIP檔案,使用裡邊的圖檔來很快的完成這個煩人的注冊過程。
當你完成後,點選Save按鈕,如果一切都OK的話,你會得到以下提示:
iOS GameCenter 挑戰,排名 歡呼!你已經在iTunes Connect中注冊了你的app并完成了最敷衍了事的部分,哈哈。接下來還需要幾個小步驟來激活Game Center。不要慌張,因為最麻煩的部分已經過去了。☺
啟用GAME CENTER的功能 點選藍色的Manage Game Center按鈕并點選Enable for Single Game Button。太棒了!你已經為你的遊戲啟用了Game Center了。這個步驟簡單到隻需要點選按鈕 – 不過别高興的太早,之後你還是需要寫很多的代碼才行哦。:]
iOS GameCenter 挑戰,排名 你還沒有完成本部分,還要添加一個leaderboard(排行榜)才算完。你可能會問這篇教程不是講challenges的嗎,跟leaderboard有什麼關系呢,别急,稍後你就明白了!
使用challenge要求添加一個leaderboard。點選Add Leaderboard按鈕并選擇Single Leaderboard類型。之後你會看到一個如下的表格:
iOS GameCenter 挑戰,排名 在leaderboard reference name欄輸入High Scores,leaderboard ID欄輸入HighScores。
注意: 一般來說,我推薦你使用包名字的擴充作為leaderboard或者achievement(成就)的ID。例如,以上的名字就是 com.ali.MonkeyJump.HighScores(你需要把com.ali替換為你自己的)。但是為了本篇教學的簡化,直接把它命名為 HighScores(而不是加上域名的字首)。
把Sort Order設定為High to Low,Score Format Type設定為Integer。最後,點選Add Language按鈕。為language details添入以下内容:
iOS GameCenter 挑戰,排名 此處添加圖檔不是雖然強制的,但是添加它是個很好的實踐機會。這裡你需要使用的資源是iTunes resource檔案夾中名字為icon_leaderboard_512.png,把它用作高分排行榜的icon。當你完成後,點選Save。
最後,點選Done按鈕。到這裡,一個leaderboard的配置就完成了,以後如果你想添加更多的,你就可以随心所欲了。
驗證本地玩家 在你開始寫代碼之前,你需要首先import(導入)GameKit framework。在 Xcode 4.5 中打開MonkeyJump工程并進入target設定。打開Build Phases子頁面,選擇Link Binary With Libraries部分。點選 “+” 按鈕,選擇導入GameKit framework到工程中。
iOS GameCenter 挑戰,排名 接下來你需要寫一些代碼來驗證使用者。注意如果你不驗證使用者,你是不能夠使用任何Game Center提供的很棒的功能的。
這裡的Player代表目前正在玩兒你的遊戲玩家。在Game Center的術語裡,這由GKLocalPlayer表示。
驗證過程簡單的分為兩個步驟:
首先你調用一個authenticate call 到 Game Center平台。 平台會異步的處理你的請求,結束後會調用一個回調函數。如果玩家已經登入了(95%的情況),一個歡迎的橫幅會彈出來,如果玩家沒登入,那麼一個允許玩家注冊的登入界面會彈出來。 我們這就寫些代碼。這裡我們使用一個單例模式,也就是說所有的Game Center的代碼都在一個類中。
在Xcode中,右鍵點選MonkeyJump group,選擇New Group。把新group命名為GameKitFiles。然後,右鍵點選新建立的這個group并選擇New File…,檔案模版選擇Objective-C Class template。把檔案命名為GameKitHelper,同時繼承NSObject。
iOS GameCenter 挑戰,排名 把GameKitHelper.h中的内容替換為以下内容:
// Include the GameKit framework
#import <GameKit/GameKit.h>
// Protocol to notify external
// objects when Game Center events occur or
// when Game Center async tasks are completed
@protocol GameKitHelperProtocol<NSObject>
@end
@interface GameKitHelper : NSObject
@property (nonatomic, assign)
id<GameKitHelperProtocol> delegate;
// This property holds the last known error
// that occured while using the Game Center API's
@property (nonatomic, readonly) NSError* lastError;
+ (id) sharedGameKitHelper;
// Player authentication, info
-(void) authenticateLocalPlayer;
@end
以上代碼自說明性很強并且有很詳細的注釋。你在此所做的無非就是聲明兩個方法和兩個屬性,其中一個屬性是delegate,另外一個會記錄下最近一次使用GameKit framework報出的錯誤。
切換到GameKitHelper.m并把檔案替換為以下内容:
#import "GameKitHelper.h"
#import "GameConstants.h"
@interface GameKitHelper ()
<GKGameCenterControllerDelegate> {
BOOL _gameCenterFeaturesEnabled;
}
@end
@implementation GameKitHelper
#pragma mark Singleton stuff
+(id) sharedGameKitHelper {
static GameKitHelper *sharedGameKitHelper;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedGameKitHelper =
[[GameKitHelper alloc] init];
});
return sharedGameKitHelper;
}
#pragma mark Player Authentication
-(void) authenticateLocalPlayer {
GKLocalPlayer* localPlayer =
[GKLocalPlayer localPlayer];
localPlayer.authenticateHandler =
^(UIViewController *viewController,
NSError *error) {
[self setLastError:error];
if ([CCDirector sharedDirector].isPaused)
[[CCDirector sharedDirector] resume];
if (localPlayer.authenticated) {
_gameCenterFeaturesEnabled = YES;
} else if(viewController) {
[[CCDirector sharedDirector] pause];
[self presentViewController:viewController];
} else {
_gameCenterFeaturesEnabled = NO;
}
};
}
@end
這裡你聲明了一個名為_gameCenterFeaturesEnabled的變量。這個BOOL類型的變量會辨別驗證是否成功。
iOS 6.0中驗證玩家的方式有所改變。所有你需要的就是設定GKLocalPlayer對象的authenticationHandler屬性,正如你在authenticateLocalPlayer方法中看到的。authenticationHandler block有兩個參數,它是被Game Center平台自動調用的。
這個block被系統在以下情形中被調用:
當你設定了authenticationHandler并發出了驗證玩家的請求。 當app進入foreground(前台)。 在登入時,例如玩家在進入遊戲前沒有登入,進入時會彈出登入界面,在這個界面中的任何互動都會調用authenticationHandler。 authenticationHandler有兩個參數:
第一個是 UIViewController ,它代表如果你未登入Game Center,需要你彈出的登入view controller。 還有一個 NSError 表示驗證過程中發生的任何錯誤。 值得注意的是,在這個block中,你首先檢查玩家是否驗證過了,如果玩家已經被驗證了,你需要做的就是把_gameCenterEnabled變量置為true,然後就可以繼續遊戲了。
如果login view controller(authenticationHandler block中的第一個參數)不為nil,就意味着玩家還沒有登入Game Center。如果是這種情況,你先暫停遊戲,然後為玩家彈出登入的view controller。如果玩家在此界面登入成功或者點選Cancel按鈕,以上那個handler block還會被調用一次。
在老版本的Game Center中,開發者是沒法決定何時為玩家彈出登入界面的。這個新方法給予了開發者更多可控性,來決定在何時彈出此界面。
最終,如果驗證失敗了,你需要恰當的禁用所有Game Center的功能。這裡通過把_gameCenterFeaturesEnabled變量置為false來讓app無視Game Center的功能調用。
為了讓authenticateLocalPlayer起作用,你還需要一些代碼。在GameKitHelper.m中加入以下内容:
#pragma mark Property setters
-(void) setLastError:(NSError*)error {
_lastError = [error copy];
if (_lastError) {
NSLog(@"GameKitHelper ERROR: %@", [[_lastError userInfo]
description]);
}
}
#pragma mark UIViewController stuff
-(UIViewController*) getRootViewController {
return [UIApplication
sharedApplication].keyWindow.rootViewController;
}
-(void)presentViewController:(UIViewController*)vc {
UIViewController* rootVC = [self getRootViewController];
[rootVC presentViewController:vc animated:YES
completion:nil];
}
以上代碼實作了authenticateLocalPlayer需要的三個函數:
lastError屬性被聲明為readonly。是以你不能在直接設定它的值,需要手動為其添加一個setter方法。這就是setLastError:的作用。 Game Center登入controller需要真實顯示出來,這樣玩家才能做登入操作。presentViewController: 和 getRootViewController方法負責得到root view controller并且通過root view controller把登入界面顯示出來。 太棒了!現在是時候測試一下GameKitHelper了。打開Prefix.pch并加入必要的import:
#import "GameKitHelper.h"
接下來,打開MenuLayer.m并在onEnter(緊跟在[super onEnter]語句之後)中加入如下内容。每當主界面顯示的時候都會驗證玩家。
[[GameKitHelper sharedGameKitHelper]
authenticateLocalPlayer];
編譯并運作。當主菜單顯示出來的時候你會看到以下的内容:
iOS GameCenter 挑戰,排名 左邊的圖示示範了登入view controller(玩家未登入Game Center的情況)。右邊的示範了welcome banner,每當驗證成功時就會彈出。
注意: 為了測試驗證過程,首先登出Game Center然後再在MonkeyJump app裡登入。隻有這樣才能在沙盒模式下運作Game Center。另外,在模拟器上運作也許行不通(至少在寫這篇教程時還不行)。你需要在真機上進行測試。
送出分數到Game Center 若送出一個分數到Game Center,需要使用GKScore類。這個類儲存着有關玩家分數和分數所屬類别的資訊。
類别指的是leaderboard ID。例如,你希望送出一個分數到High Scores排行榜,那麼GKScore對象的category就應該是你在iTues Connect設定的那個leaderboard ID,這裡就是HighScores。
打開GameKitHelper.h并加入以下方法聲明:
// Scores
-(void) submitScore:(int64_t)score
category:(NSString*)category;
接下來,在GameKitHelperProtocol中加入以下方法聲明:
-(void) onScoresSubmitted:(bool)success;
打開GameKitHelper.m并加入以下代碼:
-(void) submitScore:(int64_t)score
category:(NSString*)category {
//1: Check if Game Center
// features are enabled
if (!_gameCenterFeaturesEnabled) {
CCLOG(@"Player not authenticated");
return;
}
//2: Create a GKScore object
GKScore* gkScore =
[[GKScore alloc]
initWithCategory:category];
//3: Set the score value
gkScore.value = score;
//4: Send the score to Game Center
[gkScore reportScoreWithCompletionHandler:
^(NSError* error) {
[self setLastError:error];
BOOL success = (error == nil);
if ([_delegate
respondsToSelector:
@selector(onScoresSubmitted:)]) {
[_delegate onScoresSubmitted:success];
}
}];
}
以下是上面方法的分步說明:
檢查Game Center功能是否啟用了,隻有當啟用時再執行之後的代碼。 建立一個GKScore的執行個體。GKScore所需要的分數所屬類别作為init方法的參數傳入。 設定GKScore的分數值。 使用reportScoreWithCompletionHandler:方法把GKScore對象發送到Game Center端。當分數被發送後,平台會調用completion handler。completion handler是一個隻有一個參數的block,在這裡是一個NSError 對象,你可以通過它來檢視分數是否發送成功了。 現在你已經有了發送分數到Game Center的方法了,是時候使用它了。在使用該方法之前,打開GameConstants.h并在檔案末尾(但在最後的#endif之前)加入以下define語句:
#define kHighScoreLeaderboardCategory @"HighScores"
然後,打開GameLayer.m并找到monkeyDead方法。根據此方法的名字透露的資訊,這個方法是在猴子挂掉時調用的。換句話說,就是遊戲結束的時候。
在該方法的開頭加入以下語句:
[[GameKitHelper sharedGameKitHelper]
submitScore:(int64_t)_distance
category:kHighScoreLeaderboardCategory];
編譯并運作。玩兒一遍遊戲直到猴子挂掉。可憐的小家夥!
當你玩兒完後,你的分數會被發送到Game Center。為了驗證是否真的發送成功了,打開Game Center app,點選Games分頁并選擇MonkeyJump。這裡的排行榜會顯示你的分數。下邊是HighScores排行榜的截圖示例:
iOS GameCenter 挑戰,排名 你打敗我的分數了嗎?不要使用改代碼的作弊手段哦!:]
Game Center 挑戰 終于到了你期待已久的部分了!
Game Center 挑戰是iOS 6.0 的 Game Center中引入的最大的功能。挑戰可以讓你的遊戲病毒式地傳播,而且還可以極大的增加玩家的留存。
但是問題是,使用挑戰功能是一件異常艱難并且複雜的工作,因為它有着數量廣袤的的API而且非常複雜(作者開玩笑說的)。
iOS GameCenter 挑戰,排名 隻是開個玩笑啦!把挑戰功能內建到你的遊戲中,所有要做的僅僅是…完全不需要任何工作!☺如果你的遊戲支援leaderboards或者achievements,那麼你的遊戲就會自動的支援challenges(挑戰),而不需要做任何額外的工作。
iOS GameCenter 挑戰,排名 為了測試這個,打開Game Center程式(確定你是在沙盒模式下)。進入Games分頁并打開MonkeyJump遊戲。
如果你已經玩兒了有幾次遊戲,從leaderboard中選擇你的高分。你會看到在一個Challenge Friends的按鈕出現在詳細資訊界面。點選它,輸入想要挑戰的好友的名字,點選Send。當challenge被成功發送後,你的好友會收到一個 push notification。
注意: 為了測試challenges,你需要兩台運作iOS 6.0的裝置,每台都需要登入不同的Game Center帳号,并且互相之間要加為好友。
Challenges絕不僅僅是push notifications而已。讓我通過一個例子來詳細地為你說明它。
假如我給Ray發送了一個500米成績的挑戰。Ray會在他的裝置上接收到一個通知他此挑戰的push notification。我們假設Ray在回應挑戰的遊戲中得到了1000米的成績。也就是說Ray挑戰成功了。那麼他當然想讓我知道這件事兒。
由于遊戲是把所有分數發送到Game Center上的,Game Center自動地擷取到Ray挑戰成功了,是以它會同時發送一個挑戰完成的push notification到兩個人的裝置上去。Ray之後還可以以1000米這個分數向我發起挑戰。他一定不知道我再夢裡也能跑1000米吧。
iOS GameCenter 挑戰,排名 這個過程可以無窮盡的持續下去,每一次一方都會超過另一方的分數。這樣就會讓人很上瘾,這種自我延續的特性使得每個遊戲開發者都應該在他/她的遊戲中內建challenge。
到現在為止,你已經在Game Center應用中測試了challenge,但是怎麼樣才能允許玩家在遊戲内也能想他/她的好友發起挑戰呢?
這就是接下來要做的。:]你将要在你的遊戲中添加一個朋友選擇器,允許玩家選擇他/她的好友,并發送挑戰。
打開GameKitHelper.h并加入一個新的屬性。
@property (nonatomic, readwrite)
BOOL includeLocalPlayerScore;
接下來在GameKitHelperProtocol加入以下方法聲明:
-(void) onScoresOfFriendsToChallengeListReceived:
(NSArray*) scores;
-(void) onPlayerInfoReceived:
(NSArray*)players;
同時在GameKitHelper中加入以下方法聲明:
-(void) findScoresOfFriendsToChallenge;
-(void) getPlayerInfo:(NSArray*)playerList;
-(void) sendScoreChallengeToPlayers:
(NSArray*)players
withScore:(int64_t)score
message:(NSString*)message;
然後在GameKitHelper.m中定義以上的方法。讓我們從findScoresOfFriendsToChallenge開始。添加以下内容:
-(void) findScoresOfFriendsToChallenge {
//1
GKLeaderboard *leaderboard =
[[GKLeaderboard alloc] init];
//2
leaderboard.category =
kHighScoreLeaderboardCategory;
//3
leaderboard.playerScope =
GKLeaderboardPlayerScopeFriendsOnly;
//4
leaderboard.range = NSMakeRange(1, 100);
//5
[leaderboard
loadScoresWithCompletionHandler:
^(NSArray *scores, NSError *error) {
[self setLastError:error];
BOOL success = (error == nil);
if (success) {
if (!_includeLocalPlayerScore) {
NSMutableArray *friendsScores =
[NSMutableArray array];
for (GKScore *score in scores) {
if (![score.playerID
isEqualToString:
[GKLocalPlayer localPlayer]
.playerID]) {
[friendsScores addObject:score];
}
}
scores = friendsScores;
}
if ([_delegate
respondsToSelector:
@selector
(onScoresOfFriendsToChallengeListReceived:)]) {
[_delegate
onScoresOfFriendsToChallengeListReceived:scores];
}
}
}];
}
這個方法負責擷取玩家的所有好友的分數。通過查詢HighScores leaderboard擷取玩家的好友分數。
每次你查詢分數,Game Center都會預設添加本地玩家的分數進去。例如上邊的方法,當你擷取所有好友的分數的同時,Game Center傳回的數組不但包含所有玩家好友的,也會包含玩家自身的分數。是以這裡你使用了includeLocalPlayerScore屬性來決定是 否要添加玩家自己的分數到傳回結果中,預設的這個值是NO(不包含玩家的分數)。
現在添加以下方法:
-(void) getPlayerInfo:(NSArray*)playerList {
//1
if (_gameCenterFeaturesEnabled == NO)
return;
//2
if ([playerList count] > 0) {
[GKPlayer
loadPlayersForIdentifiers:
playerList
withCompletionHandler:
^(NSArray* players, NSError* error) {
[self setLastError:error];
if ([_delegate
respondsToSelector:
@selector(onPlayerInfoReceived:)]) {
[_delegate onPlayerInfoReceived:players];
}
}];
}
}
此方法通過傳入一個玩家ID的數組來獲得這些玩家的資訊。
還有最後一個方法 – 添加以下代碼:
-(void) sendScoreChallengeToPlayers:
(NSArray*)players
withScore:(int64_t)score
message:(NSString*)message {
//1
GKScore *gkScore =
[[GKScore alloc]
initWithCategory:
kHighScoreLeaderboardCategory];
gkScore.value = score;
//2
[gkScore issueChallengeToPlayers:
players message:message];
}
此方法向一組玩家發送一個分數挑戰,同時還跟随着一條玩家發送的消息。
很好!接下來,你需要一個friend picker(玩家選擇器)。friend picker将會允許玩家輸入一條自定義的消息并選擇他/她想要發送挑戰的玩家們。預設情況下,它會選擇那些目前分數比你低的玩家,因為這些人玩家理所應 當向他們發送挑戰。畢竟每個玩家都希望赢!☺
在Xcode中建立一個group并命名為ViewControllers。然後建立一個繼承自UIViewController的檔案并将其命名為FriendsPickerViewController。注意這裡要選中“With XIB for user interface”。如下所示:
iOS GameCenter 挑戰,排名 打開FriendsPickerViewController.xib檔案,設定view’s orientation為landscape,拖拽進來一個UITableView,一個UITextField和一個UILabel到canvas中,設定label的text屬性為“Challenge message”。
另外,為了讓次界面看起來和遊戲的其他界面相吻合,添加bg_menu.png作為背景圖檔。最終的view controller看起來如下圖所示:
iOS GameCenter 挑戰,排名 打開FriendsPickerViewController.h并在@interface添加如下語句:
typedef void
(^FriendsPickerCancelButtonPressed)();
typedef void
(^FriendsPickerChallengeButtonPressed)();
這兩個新的資料類型,FriendsPickerCancelButtonPressed 和 FriendsPickerChallengeButtonPressed,是你将要使用的兩種block。block類似C函數,它有傳回類型(這裡是 void)和零個或多個參數。typedef定義使之後在代碼中使用此block更為簡化。
添加如下屬性到@interface部分:
//1
@property (nonatomic, copy)
FriendsPickerCancelButtonPressed
cancelButtonPressedBlock;
//2
@property (nonatomic, copy)
FriendsPickerChallengeButtonPressed
challengeButtonPressedBlock;
這些屬性是将來Cancel或者Challenge按鈕按下時所執行的block。
接下來添加Cancel和Challenge按鈕到view controller中。打開FriendsPickerViewController.m并替換viewDidLoad為以下代碼:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *cancelButton =
[[UIBarButtonItem alloc]
initWithTitle:@"Cancel"
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancelButtonPressed:)];
UIBarButtonItem *challengeButton =
[[UIBarButtonItem alloc]
initWithTitle:@"Challenge"
style:UIBarButtonItemStylePlain
target:self
action:@selector(challengeButtonPressed:)];
self.navigationItem.leftBarButtonItem =
cancelButton;
self.navigationItem.rightBarButtonItem =
challengeButton;
}
此方法添加了兩個UIBarButtonItems到view controller中,分别是Cancel和Challenge按鈕。現在添加當這兩個按鈕被按下時所觸發的方法。
- (void)cancelButtonPressed:(id) sender {
if (self.cancelButtonPressedBlock != nil) {
self.cancelButtonPressedBlock();
}
}
- (void)challengeButtonPressed:(id) sender {
if (self.challengeButtonPressedBlock) {
self.challengeButtonPressedBlock();
}
}
上邊的方法很好了解,你所做的就是在函數中執行challenge和cancel的block。
在你把此view controller內建到遊戲中并驗證一切正常之前,你需要先寫一個初始化方法來擷取本地玩家的分數。在完成這一步之前,你先要定義一個變量存儲此分數。
在FriendsPickerViewController.m中的類extension塊兒中添加以下變量 – 記得要在變量之間插入花括号,最終的類extension看起來如下所示:
@interface FriendsPickerViewController () {
int64_t _score;
}
@end
現在添加如下的初始化方法:
- (id)initWithScore:(int64_t) score {
self = [super
initWithNibName:
@"FriendsPickerViewController"
bundle:nil];
if (self) {
_score = score;
}
return self;
}
在FriendsPickerViewController.h中添加該方法聲明,如下所示:
-(id)initWithScore:(int64_t) score;
現在你就可以測試以下此view controller了,看看它是不是如預期一樣工作正常。打開GameKitHelper.h并定義一個如下方法:
-(void)
showFriendsPickerViewControllerForScore:
(int64_t)score;
然後打開GameKitHelper.m并添加如下import語句:
#import "FriendsPickerViewController.h"
然後,添加如下方法:
-(void)
showFriendsPickerViewControllerForScore:
(int64_t)score {
FriendsPickerViewController
*friendsPickerViewController =
[[FriendsPickerViewController alloc]
initWithScore:score];
friendsPickerViewController.
cancelButtonPressedBlock = ^() {
[self dismissModalViewController];
};
friendsPickerViewController.
challengeButtonPressedBlock = ^() {
[self dismissModalViewController];
};
UINavigationController *navigationController =
[[UINavigationController alloc]
initWithRootViewController:
friendsPickerViewController];
[self presentViewController:navigationController];
}
此方法會模态地彈出FriendPickerController。它還定義了當Challeng和Cancel按鈕被按下時觸發的block。目前它們隻是簡單的使該界面消失。
打開GameOverLayer.m并把menuButtonPressed:中的CCLOG(@”Challenge button pressed”);行替換為以下内容:
[[GameKitHelper sharedGameKitHelper]
showFriendsPickerViewControllerForScore:_score];
到了關鍵時刻了!編譯并運作,玩兒一局MonkeyJump,在game over屏點選Challenge Friends按鈕,你會看到FriendsPickerViewController彈出來了。如果你點選不論Challenge還是Cancel按鈕 都會使該界面消失。
iOS GameCenter 挑戰,排名 很好!你的遊戲現在有了好友選擇的界面了。但是這個界面還沒顯示任何的好友,這樣是不行的。
沒必要感覺孤單,我們這就加入此功能!
打開FriendsPickerViewController.m并把類extension替換為以下内容:
@interface FriendsPickerViewController ()
<UITableViewDataSource,
UITableViewDelegate,
UITextFieldDelegate,
GameKitHelperProtocol> {
NSMutableDictionary *_dataSource;
int64_t _score;
}
@property (nonatomic, weak)
IBOutlet UITableView *tableView;
@property (nonatomic, weak)
IBOutlet UITextField *challengeTextField;
@end
注意這裡的interface部分實作了很多的protocol。同時,它還有兩個IBOutlet,一個是UITableView的,另一個是UITextfield的。使用Interface Builder把它們和相應的view關聯起來,如下所示:
iOS GameCenter 挑戰,排名 接下來設定UITableView的delegate和data source,還有UITextField的delegate,在Interface Builder中把它們都設定為File’s Owner。
為了完成這個,首先選擇UITableView,在Connections inspector中,把data source和delegate outlet分别拖拽到左側的File’s Owner,如下圖所示:
iOS GameCenter 挑戰,排名 為UITextField重複此步驟。
切換到FriendsPickerViewController.m并在initWithScore:方法的if語句中的_score = score;行之後添加如下代碼:
dataSource = [NSMutableDictionary dictionary];
GameKitHelper *gameKitHelper = [GameKitHelper sharedGameKitHelper];
gameKitHelper.delegate = self;
[gameKitHelper findScoresOfFriendsToChallenge];
此方法初始化了data source,設定其自身為GameKitHelper的delegate并調用findScoresOfFriendsToChallenge。如果你 還記得,這個方法是用來擷取本地玩家所有好友的分數的。接下來需要實作 onScoresOfFriendsToChallengeListReceived:代理方法,來處理當玩家的分數擷取到後的事件:
-(void)
onScoresOfFriendsToChallengeListReceived:
(NSArray*) scores {
//1
NSMutableArray *playerIds =
[NSMutableArray array];
//2
[scores enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop){
GKScore *score = (GKScore*) obj;
//3
if(_dataSource[score.playerID]
== nil) {
_dataSource[score.playerID] =
[NSMutableDictionary dictionary];
[playerIds addObject:score.playerID];
}
//4
if (score.value < _score) {
[_dataSource[score.playerID]
setObject:[NSNumber numberWithBool:YES]
forKey:kIsChallengedKey];
}
//5
[_dataSource[score.playerID]
setObject:score forKey:kScoreKey];
}];
//6
[[GameKitHelper sharedGameKitHelper]
getPlayerInfo:playerIds];
[self.tableView reloadData];
}
以上代碼有很強的自說明性,不過還是按步驟解釋一下:
建立一個名為playerIds的數組用來存儲本地玩家所有好友的ID。 然後此方法開始周遊傳回的分數。 對每一個分數,都在data source中建立相應的資料,并且在playerIds數組中儲存player ID。 如果這個分數比本地玩家的分數低,該分數對應的在data source中的資料會被标記。 分數被儲存在data source字典中。 GameKitHelper的getPlayerInfo:方法調用時使用playerIds數組作為參數。該方法會傳回每個好友的詳細資訊,比如好友的名字和頭像。 以上代碼需要一些#define語句才能正常工作,在檔案頭#import行之後,加入以下内容(有些是以後會用到的):
#define kPlayerKey @"player"
#define kScoreKey @"score"
#define kIsChallengedKey @"isChallenged"
#define kCheckMarkTag 4
然後你需要實作onPlayerInfoReceived:代理方法。這個方法會在本地玩家所有好友的資訊擷取到之後調用。
-(void) onPlayerInfoReceived:(NSArray*)players {
//1
[players
enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
GKPlayer *player = (GKPlayer*)obj;
//2
if (_dataSource[player.playerID]
== nil) {
_dataSource[player.playerID] =
[NSMutableDictionary dictionary];
}
[_dataSource[player.playerID]
setObject:player forKey:kPlayerKey];
//3
[self.tableView reloadData];
}];
}
這個方法也非常直接了當,因為你有每個玩家的詳細資訊,你隻需要更新每個玩家的_dataSource字典即可。
_dataSource字典用來作為table view的資料源。接下來實作table view的data source方法,如下所示:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return _dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell identifier";
static int ScoreLabelTag = 1;
static int PlayerImageTag = 2;
static int PlayerNameTag = 3;
UITableViewCell *tableViewCell =
[tableView
dequeueReusableCellWithIdentifier:
CellIdentifier];
if (!tableViewCell) {
tableViewCell =
[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
tableViewCell.selectionStyle =
UITableViewCellSelectionStyleGray;
tableViewCell.textLabel.textColor =
[UIColor whiteColor];
UILabel *playerName =
[[UILabel alloc] initWithFrame:
CGRectMake(50, 0, 150, 44)];
playerName.tag = PlayerNameTag;
playerName.font = [UIFont systemFontOfSize:18];
playerName.backgroundColor =
[UIColor clearColor];
playerName.textAlignment =
UIControlContentVerticalAlignmentCenter;
[tableViewCell addSubview:playerName];
UIImageView *playerImage =
[[UIImageView alloc]
initWithFrame:CGRectMake(0, 0, 44, 44)];
playerImage.tag = PlayerImageTag;
[tableViewCell addSubview:playerImage];
UILabel *scoreLabel =
[[UILabel alloc]
initWithFrame:
CGRectMake(395, 0, 30,
tableViewCell.frame.size.height)];
scoreLabel.tag = ScoreLabelTag;
scoreLabel.backgroundColor =
[UIColor clearColor];
scoreLabel.textColor =
[UIColor whiteColor];
[tableViewCell.contentView
addSubview:scoreLabel];
UIImageView *checkmark =
[[UIImageView alloc]
initWithImage:[UIImage
imageNamed:@"checkmark.png"]];
checkmark.tag = kCheckMarkTag;
checkmark.hidden = YES;
CGRect frame = checkmark.frame;
frame.origin =
CGPointMake(tableView.frame.size.width - 16, 13);
checkmark.frame = frame;
[tableViewCell.contentView
addSubview:checkmark];
}
NSDictionary *dict =
[_dataSource allValues][indexPath.row];
GKScore *score = dict[kScoreKey];
GKPlayer *player = dict[kPlayerKey];
NSNumber *number = dict[kIsChallengedKey];
UIImageView *checkmark =
(UIImageView*)[tableViewCell
viewWithTag:kCheckMarkTag];
if ([number boolValue] == YES) {
checkmark.hidden = NO;
} else {
checkmark.hidden = YES;
}
[player
loadPhotoForSize:GKPhotoSizeSmall
withCompletionHandler:
^(UIImage *photo, NSError *error) {
if (!error) {
UIImageView *playerImage =
(UIImageView*)[tableView
viewWithTag:PlayerImageTag];
playerImage.image = photo;
} else {
NSLog(@"Error loading image");
}
}];
UILabel *playerName =
(UILabel*)[tableViewCell
viewWithTag:PlayerNameTag];
playerName.text = player.displayName;
UILabel *scoreLabel =
(UILabel*)[tableViewCell
viewWithTag:ScoreLabelTag];
scoreLabel.text = score.formattedValue;
return tableViewCell;
}
好多的代碼啊。:]但是你之前使用過UITableView,這些代碼對你并不陌生。tableView:cellForRowAtIndex:建立一個新的UITableViewCell。每個table view中的cell都會包含一個頭像,玩家的名字和分數。
現在添加tableView:didSelectRowAtIndex:來處理使用者選擇table view中每一行的事件:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
BOOL isChallenged = NO;
//1
UITableViewCell *tableViewCell =
[tableView cellForRowAtIndexPath:
indexPath];
//2
UIImageView *checkmark =
(UIImageView*)[tableViewCell
viewWithTag:kCheckMarkTag];
//3
if (checkmark.isHidden == NO) {
checkmark.hidden = YES;
} else {
checkmark.hidden = NO;
isChallenged = YES;
}
NSArray *array =
[_dataSource allValues];
NSMutableDictionary *dict =
array[indexPath.row];
//4
[dict setObject:[NSNumber
numberWithBool:isChallenged]
forKey:kIsChallengedKey];
[tableView deselectRowAtIndexPath:indexPath
animated:YES];
}
這個方法所做的是設定_dataSource的entry為YES或者NO。
編譯并運作。到這裡FriendsPickerViewController就可以顯示出帶有本地玩家的好友資訊的UITableView了。每個好友的詳細資訊,比如名字和頭像,也會被顯示在每個cell中。如下圖所示:
iOS GameCenter 挑戰,排名 最後一件要做的事就是實際發送挑戰了。把FriendsPickerViewController.m中的challengeButtonPressed:替換為以下内容:
- (void)challengeButtonPressed:
(id) sender {
//1
if(self.challengeTextField.text.
length > 0) {
//2
NSMutableArray *playerIds =
[NSMutableArray array];
NSArray *allValues =
[_dataSource allValues];
for (NSDictionary *dict in allValues) {
if ([dict[kIsChallengedKey]
boolValue] == YES) {
GKPlayer *player =
dict[kPlayerKey];
[playerIds addObject:
player.playerID];
}
}
if (playerIds.count > 0) {
//3
[[GameKitHelper sharedGameKitHelper]
sendScoreChallengeToPlayers:playerIds
withScore:_score message:
self.challengeTextField.text];
}
if (self.challengeButtonPressedBlock) {
self.challengeButtonPressedBlock();
}
} else {
self.challengeTextField.layer.
borderWidth = 2;
self.challengeTextField.layer.
borderColor =
[UIColor redColor].CGColor;
}
}
以下是上面方法的詳細步驟分解:
此方法首先檢查玩家是否輸入了消息。如果沒有,就把challengeTextField的邊框設為紅色。 如果使用者輸入了文本,此方法擇查找所有被選中的玩家ID,并把它們儲存到playerIds數組中。 如果使用者選擇了一個火一個以上的玩家挑戰的話,則使用玩家ID作為參數調用GameKitHelper的sendScoreChallengeToPlayers:withScore:方法。此方法會發送挑戰給所有已選擇的玩家。 編譯并運作遊戲。現在當你點選FriendsPickerViewController界面的Challenge Friends按鈕時,它會發送一個分數挑戰。如果你有兩台裝置,你就可以輕易地測試它們是否工作正常了。