你也许曾听说过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非常容易上手,即使是你的妈妈也能玩儿!只需要轻点屏幕就可以让猴子跳过敌人了。游戏会记录猴子跑的距离并以此为玩家打分。
亲自是玩儿一下吧!把starter项目解压,在Xcode中编译并运行。试试看你能跑多远!:]
另外简单过一遍代码,看看这些层和场景之间是如何协调工作的。
在做任何有关Game Center Challenges功能之前,首先要做的就是配置你的app使用Game Center!这个过程需要三个步骤:
让我们按顺序过一遍这些步骤。对于许多有Game Center经验的读者来说,这会是相当熟悉的,但我保证我会很快的讲完这一部分。
第一步,你需要创建一个App ID。登录到iOS Dev Center,选择iOS Provisioning Portal。
在Provisioning Portal中,选择App IDs,选择create a new App ID。使用monkeyjump作为游戏名字并输入一个bundle identifier,通常这里使用倒转的DNS命名,比如com.ali.MonkeyJump(如果你没有自己的域名,你可以使用你自己的名字代 替)。
当你完成后,点击Submit按钮。打开MonkeyJump Xcode工程,选择project root,然后选择MonkeyJump target,在Summary tab中把Bundle Identifier替换为你刚刚在Provisioning Portal中创建的那个。
编译并运行,在真机上运行试试看。如果一切配置都正确的话,游戏应该立刻启动。如果没有,那么clean一下项目并重新编译一次。
接下来的步骤是iTunes Connect中创建一个新app。首先登录到iTunes Connect,切换到application management子页面,点击位于左上角的Add New App按钮。(如果你同时拥有Mac和iOS的开发者帐号,你可能需要选择app的类型 – 当然要选择iOS)。
在第一个屏幕中,输入MonkeyJump作为游戏名字,400作为SKU number(SKU number可以是任意的数字/单词,你也可以设置成你想要的)并选择上一步中创建的Bundle Identifier。
当你输入完所有值后,点击Continue。在弹出的提示框中输入所有需要的信息。因为你只需要在本教程中使用此项目,所以一切从简,只填入强制要求填入的项目。?
你需要上传一个大的app icon 和一个截图。为了让过程更容易,我为你准备好了iTunes resources file。你可以解压这个ZIP文件,使用里边的图片来很快的完成这个烦人的注册过程。
当你完成后,点击Save按钮,如果一切都OK的话,你会得到以下提示:
欢呼!你已经在iTunes Connect中注册了你的app并完成了最敷衍了事的部分,哈哈。接下来还需要几个小步骤来激活Game Center。不要慌张,因为最麻烦的部分已经过去了。?
点击蓝色的Manage Game Center按钮并点击Enable for Single Game Button。太棒了!你已经为你的游戏启用了Game Center了。这个步骤简单到只需要点击按钮 – 不过别高兴的太早,之后你还是需要写很多的代码才行哦。:]
你还没有完成本部分,还要添加一个leaderboard(排行榜)才算完。你可能会问这篇教程不是讲challenges的吗,跟leaderboard有什么关系呢,别急,稍后你就明白了!
使用challenge要求添加一个leaderboard。点击Add Leaderboard按钮并选择Single Leaderboard类型。之后你会看到一个如下的表格:
在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添入以下内容:
此处添加图片不是虽然强制的,但是添加它是个很好的实践机会。这里你需要使用的资源是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到工程中。
接下来你需要写一些代码来验证用户。注意如果你不验证用户,你是不能够使用任何Game Center提供的很棒的功能的。
这里的Player代表当前正在玩儿你的游戏玩家。在Game Center的术语里,这由GKLocalPlayer表示。
验证过程简单的分为两个步骤:
我们这就写些代码。这里我们使用一个单例模式,也就是说所有的Game Center的代码都在一个类中。
在Xcode中,右键点击MonkeyJump group,选择New Group。把新group命名为GameKitFiles。然后,右键点击新创建的这个group并选择New File…,文件模版选择Objective-C Class template。把文件命名为GameKitHelper,同时继承NSObject。
把GameKitHelper.h中的内容替换为以下内容:
// Include the GameKit framework #import |
以上代码自说明性很强并且有很详细的注释。你在此所做的无非就是声明两个方法和两个属性,其中一个属性是delegate,另外一个会记录下最近一次使用GameKit framework报出的错误。
切换到GameKitHelper.m并把文件替换为以下内容:
#import "GameKitHelper.h" #import "GameConstants.h" @interface GameKitHelper () |
这里你声明了一个名为_gameCenterFeaturesEnabled的变量。这个BOOL类型的变量会标识验证是否成功。
iOS 6.0中验证玩家的方式有所改变。所有你需要的就是设置GKLocalPlayer对象的authenticationHandler属性,正如你在authenticateLocalPlayer方法中看到的。authenticationHandler block有两个参数,它是被Game Center平台自动调用的。
这个block被系统在以下情形中被调用:
authenticationHandler有两个参数:
值得注意的是,在这个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需要的三个函数:
太棒了!现在是时候测试一下GameKitHelper了。打开Prefix.pch并加入必要的import:
#import "GameKitHelper.h" |
接下来,打开MenuLayer.m并在onEnter(紧跟在[super onEnter]语句之后)中加入如下内容。每当主界面显示的时候都会验证玩家。
[[GameKitHelper sharedGameKitHelper] authenticateLocalPlayer]; |
编译并运行。当主菜单显示出来的时候你会看到以下的内容:
左边的图示演示了登录view controller(玩家未登录Game Center的情况)。右边的演示了welcome banner,每当验证成功时就会弹出。
注意: 为了测试验证过程,首先登出Game Center然后再在MonkeyJump app里登录。只有这样才能在沙盒模式下运行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的方法了,是时候使用它了。在使用该方法之前,打开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排行榜的截图示例:
你打败我的分数了吗?不要使用改代码的作弊手段哦!:]
终于到了你期待已久的部分了!
Game Center 挑战是iOS 6.0 的 Game Center中引入的最大的功能。挑战可以让你的游戏病毒式地传播,而且还可以极大的增加玩家的留存。
但是问题是,使用挑战功能是一件异常艰难并且复杂的工作,因为它有着数量广袤的的API而且非常复杂(作者开玩笑说的)。
只是开个玩笑啦!把挑战功能集成到你的游戏中,所有要做的仅仅是…完全不需要任何工作!?如果你的游戏支持leaderboards或者achievements,那么你的游戏就会自动的支持challenges(挑战),而不需要做任何额外的工作。
为了测试这个,打开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米吧。
这个过程可以无穷尽的持续下去,每一次一方都会超过另一方的分数。这样就会让人很上瘾,这种自我延续的特性使得每个游戏开发者都应该在他/她的游戏中集成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”。如下所示:
打开FriendsPickerViewController.xib文件,设置view’s orientation为landscape,拖拽进来一个UITableView,一个UITextField和一个UILabel到canvas中,设置label的text属性为“Challenge message”。
另外,为了让次界面看起来和游戏的其他界面相吻合,添加bg_menu.png作为背景图片。最终的view controller看起来如下图所示:
打开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按钮 都会使该界面消失。
很好!你的游戏现在有了好友选择的界面了。但是这个界面还没显示任何的好友,这样是不行的。
没必要感觉孤单,我们这就加入此功能!
打开FriendsPickerViewController.m并把类extension替换为以下内容:
@interface FriendsPickerViewController () |
注意这里的interface部分实现了很多的protocol。同时,它还有两个IBOutlet,一个是UITableView的,另一个是UITextfield的。使用Interface Builder把它们和相应的view关联起来,如下所示:
接下来设置UITableView的delegate和data source,还有UITextField的delegate,在Interface Builder中把它们都设置为File’s Owner。
为了完成这个,首先选择UITableView,在Connections inspector中,把data source和delegate outlet分别拖拽到左侧的File’s Owner,如下图所示:
为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]; } |
以上代码有很强的自说明性,不过还是按步骤解释一下:
以上代码需要一些#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中。如下图所示:
最后一件要做的事就是实际发送挑战了。把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; } } |
以下是上面方法的详细步骤分解:
编译并运行游戏。现在当你点击FriendsPickerViewController界面的Challenge Friends按钮时,它会发送一个分数挑战。如果你有两台设备,你就可以轻易地测试它们是否工作正常了。