你也许曾听说过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

在做任何有关Game Center Challenges功能之前,首先要做的就是配置你的app使用Game Center!这个过程需要三个步骤:

  1. 创建并设置App ID。
  2. 在iTunes Coonect上注册你的app。
  3. 启用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(如果你没有自己的域名,你可以使用你自己的名字代 替)。

当你完成后,点击Submit按钮。打开MonkeyJump Xcode工程,选择project root,然后选择MonkeyJump target,在Summary tab中把Bundle Identifier替换为你刚刚在Provisioning Portal中创建的那个。

编译并运行,在真机上运行试试看。如果一切配置都正确的话,游戏应该立刻启动。如果没有,那么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。

当你输入完所有值后,点击Continue。在弹出的提示框中输入所有需要的信息。因为你只需要在本教程中使用此项目,所以一切从简,只填入强制要求填入的项目。☺

你需要上传一个大的app icon 和一个截图。为了让过程更容易,我为你准备好了iTunes resources file。你可以解压这个ZIP文件,使用里边的图片来很快的完成这个烦人的注册过程。

当你完成后,点击Save按钮,如果一切都OK的话,你会得到以下提示:

欢呼!你已经在iTunes Connect中注册了你的app并完成了最敷衍了事的部分,哈哈。接下来还需要几个小步骤来激活Game Center。不要慌张,因为最麻烦的部分已经过去了。☺

启用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表示。

验证过程简单的分为两个步骤:

  1. 首先你调用一个authenticate call 到 Game Center平台。
  2. 平台会异步的处理你的请求,结束后会调用一个回调函数。如果玩家已经登录了(95%的情况),一个欢迎的横幅会弹出来,如果玩家没登录,那么一个允许玩家注册的登录界面会弹出来。

我们这就写些代码。这里我们使用一个单例模式,也就是说所有的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 <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需要的三个函数:

  1. lastError属性被声明为readonly。因此你不能在直接设置它的值,需要手动为其添加一个setter方法。这就是setLastError:的作用。
  2. 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];

编译并运行。当主菜单显示出来的时候你会看到以下的内容:

左边的图示演示了登录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)scorecategory:(NSString*)category;

接下来,在GameKitHelperProtocol中加入以下方法声明:

-(void) onScoresSubmitted:(bool)success;

打开GameKitHelper.m并加入以下代码:

-(void) submitScore:(int64_t)scorecategory:(NSString*)category {//1: Check if Game Center//   features are enabledif (!_gameCenterFeaturesEnabled) {CCLOG(@"Player not authenticated");return;}//2: Create a GKScore objectGKScore* gkScore =[[GKScore alloc]initWithCategory:category];//3: Set the score valuegkScore.value = score;//4: Send the score to Game Center[gkScore reportScoreWithCompletionHandler:^(NSError* error) {[self setLastError:error];BOOL success = (error == nil);if ([_delegaterespondsToSelector:@selector(onScoresSubmitted:)]) {[_delegate onScoresSubmitted:success];}}];
}

以下是上面方法的分步说明:

  1. 检查Game Center功能是否启用了,只有当启用时再执行之后的代码。
  2. 创建一个GKScore的实例。GKScore所需要的分数所属类别作为init方法的参数传入。
  3. 设置GKScore的分数值。
  4. 使用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)_distancecategory:kHighScoreLeaderboardCategory];

编译并运行。玩儿一遍游戏直到猴子挂掉。可怜的小家伙!

当你玩儿完后,你的分数会被发送到Game Center。为了验证是否真的发送成功了,打开Game Center app,点击Games分页并选择MonkeyJump。这里的排行榜会显示你的分数。下边是HighScores排行榜的截图示例:

你打败我的分数了吗?不要使用改代码的作弊手段哦!:]

Game Center 挑战

终于到了你期待已久的部分了!

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*)playerswithScore:(int64_t)scoremessage:(NSString*)message;

然后在GameKitHelper.m中定义以上的方法。让我们从findScoresOfFriendsToChallenge开始。添加以下内容:

-(void) findScoresOfFriendsToChallenge {//1GKLeaderboard *leaderboard =[[GKLeaderboard alloc] init];//2leaderboard.category =kHighScoreLeaderboardCategory;//3leaderboard.playerScope =GKLeaderboardPlayerScopeFriendsOnly;//4leaderboard.range = NSMakeRange(1, 100);//5[leaderboardloadScoresWithCompletionHandler:^(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.playerIDisEqualToString:[GKLocalPlayer localPlayer].playerID]) {[friendsScores addObject:score];}}scores = friendsScores;}if ([_delegaterespondsToSelector:@selector(onScoresOfFriendsToChallengeListReceived:)]) {[_delegateonScoresOfFriendsToChallengeListReceived:scores];}}}];
}

这个方法负责获取玩家的所有好友的分数。通过查询HighScores leaderboard获取玩家的好友分数。

每次你查询分数,Game Center都会默认添加本地玩家的分数进去。例如上边的方法,当你获取所有好友的分数的同时,Game Center返回的数组不但包含所有玩家好友的,也会包含玩家自身的分数。所以这里你使用了includeLocalPlayerScore属性来决定是 否要添加玩家自己的分数到返回结果中,默认的这个值是NO(不包含玩家的分数)。

现在添加以下方法:

-(void) getPlayerInfo:(NSArray*)playerList {//1if (_gameCenterFeaturesEnabled == NO)return;//2if ([playerList count] > 0) {[GKPlayerloadPlayersForIdentifiers:playerListwithCompletionHandler:^(NSArray* players, NSError* error) {[self setLastError:error];if ([_delegaterespondsToSelector:@selector(onPlayerInfoReceived:)]) {[_delegate onPlayerInfoReceived:players];}}];}
}

此方法通过传入一个玩家ID的数组来获得这些玩家的信息。

还有最后一个方法 – 添加以下代码:

-(void) sendScoreChallengeToPlayers:(NSArray*)playerswithScore:(int64_t)scoremessage:(NSString*)message {//1GKScore *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)FriendsPickerCancelButtonPressedcancelButtonPressedBlock;//2
@property (nonatomic, copy)FriendsPickerChallengeButtonPressedchallengeButtonPressedBlock;

这些属性是将来Cancel或者Challenge按钮按下时所执行的block。

接下来添加Cancel和Challenge按钮到view controller中。打开FriendsPickerViewController.m并替换viewDidLoad为以下代码:

- (void)viewDidLoad {[super viewDidLoad];UIBarButtonItem *cancelButton =[[UIBarButtonItem alloc]initWithTitle:@"Cancel"style:UIBarButtonItemStylePlaintarget:selfaction:@selector(cancelButtonPressed:)];UIBarButtonItem *challengeButton =[[UIBarButtonItem alloc]initWithTitle:@"Challenge"style:UIBarButtonItemStylePlaintarget:selfaction:@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 = [superinitWithNibName:@"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 ()<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关联起来,如下所示:

接下来设置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 {//1NSMutableArray *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];}//4if (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];
}

以上代码有很强的自说明性,不过还是按步骤解释一下:

  1. 创建一个名为playerIds的数组用来存储本地玩家所有好友的ID。
  2. 然后此方法开始遍历返回的分数。
  3. 对每一个分数,都在data source中创建相应的数据,并且在playerIds数组中保存player ID。
  4. 如果这个分数比本地玩家的分数低,该分数对应的在data source中的数据会被标记。
  5. 分数被保存在data source字典中。
  6. GameKitHelper的getPlayerInfo:方法调用时使用playerIds数组作为参数。该方法会返回每个好友的详细信息,比如好友的名字和头像。

以上代码需要一些#define语句才能正常工作,在文件头#import行之后,加入以下内容(有些是以后会用到的):

#define kPlayerKey @"player"
#define kScoreKey @"score"
#define kIsChallengedKey @"isChallenged"#define kCheckMarkTag 4

然后你需要实现onPlayerInfoReceived:代理方法。这个方法会在本地玩家所有好友的信息获取到之后调用。

-(void) onPlayerInfoReceived:(NSArray*)players {//1[playersenumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {GKPlayer *player = (GKPlayer*)obj;//2if (_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 *)tableViewnumberOfRowsInSection:(NSInteger)section {return _dataSource.count;
}- (UITableViewCell *)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *CellIdentifier = @"Cell identifier";static int ScoreLabelTag = 1;static int PlayerImageTag = 2;static int PlayerNameTag = 3;UITableViewCell *tableViewCell =[tableViewdequeueReusableCellWithIdentifier:CellIdentifier];if (!tableViewCell) {tableViewCell =[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier: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.contentViewaddSubview:scoreLabel];UIImageView *checkmark =[[UIImageView alloc]initWithImage:[UIImageimageNamed:@"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.contentViewaddSubview:checkmark];}NSDictionary *dict =[_dataSource allValues][indexPath.row];GKScore *score = dict[kScoreKey];GKPlayer *player = dict[kPlayerKey];NSNumber *number = dict[kIsChallengedKey];UIImageView *checkmark =(UIImageView*)[tableViewCellviewWithTag:kCheckMarkTag];if ([number boolValue] == YES) {checkmark.hidden = NO;} else {checkmark.hidden = YES;}[playerloadPhotoForSize:GKPhotoSizeSmallwithCompletionHandler:^(UIImage *photo, NSError *error) {if (!error) {UIImageView *playerImage =(UIImageView*)[tableViewviewWithTag:PlayerImageTag];playerImage.image = photo;} else {NSLog(@"Error loading image");}}];UILabel *playerName =(UILabel*)[tableViewCellviewWithTag:PlayerNameTag];playerName.text = player.displayName;UILabel *scoreLabel =(UILabel*)[tableViewCellviewWithTag:ScoreLabelTag];scoreLabel.text = score.formattedValue;return tableViewCell;
}

好多的代码啊。:]但是你之前使用过UITableView,这些代码对你并不陌生。tableView:cellForRowAtIndex:创建一个新的UITableViewCell。每个table view中的cell都会包含一个头像,玩家的名字和分数。

现在添加tableView:didSelectRowAtIndex:来处理用户选择table view中每一行的事件:

- (void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath *)indexPath {BOOL isChallenged = NO;//1UITableViewCell *tableViewCell =[tableView cellForRowAtIndexPath:indexPath];//2UIImageView *checkmark =(UIImageView*)[tableViewCellviewWithTag:kCheckMarkTag];//3if (checkmark.isHidden == NO) {checkmark.hidden = YES;} else {checkmark.hidden = NO;isChallenged = YES;}NSArray *array =[_dataSource allValues];NSMutableDictionary *dict =array[indexPath.row];//4[dict setObject:[NSNumbernumberWithBool:isChallenged]forKey:kIsChallengedKey];[tableView deselectRowAtIndexPath:indexPathanimated:YES];
}

这个方法所做的是设置_dataSource的entry为YES或者NO。

编译并运行。到这里FriendsPickerViewController就可以显示出带有本地玩家的好友信息的UITableView了。每个好友的详细信息,比如名字和头像,也会被显示在每个cell中。如下图所示:

最后一件要做的事就是实际发送挑战了。把FriendsPickerViewController.m中的challengeButtonPressed:替换为以下内容:

- (void)challengeButtonPressed:(id) sender {//1if(self.challengeTextField.text.length > 0) {//2NSMutableArray *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:playerIdswithScore:_score message:self.challengeTextField.text];}if (self.challengeButtonPressedBlock) {self.challengeButtonPressedBlock();}} else {self.challengeTextField.layer.borderWidth = 2;self.challengeTextField.layer.borderColor =[UIColor redColor].CGColor;}
}

以下是上面方法的详细步骤分解:

  1. 此方法首先检查玩家是否输入了消息。如果没有,就把challengeTextField的边框设为红色。
  2. 如果用户输入了文本,此方法择查找所有被选中的玩家ID,并把它们保存到playerIds数组中。
  3. 如果用户选择了一个火一个以上的玩家挑战的话,则使用玩家ID作为参数调用GameKitHelper的sendScoreChallengeToPlayers:withScore:方法。此方法会发送挑战给所有已选择的玩家。

编译并运行游戏。现在当你点击FriendsPickerViewController界面的Challenge Friends按钮时,它会发送一个分数挑战。如果你有两台设备,你就可以轻易地测试它们是否工作正常了。

iOS GameCenter 挑战,排名相关推荐

  1. iOS Crash 杀手排名

    2019独角兽企业重金招聘Python工程师标准>>> 随着公司嘟嘟牛app用户数量多了起来,崩溃的问题也多了起来,最近这几天终于得空,集中时间处理了一下崩溃的问题,现总结一下,希望 ...

  2. Android 和 iOS 漏洞加剧移动安全的威胁

    漏洞面临消亡 Adobe 公司在 2005 年收购 Flash,并大力推广应用使其一度是漏洞挖掘人员的研究焦点,根据图 3.4 的历史数据可以看到,2015 和 2016 年爆出的漏洞总数占据整体数量 ...

  3. TA游戏推荐:精选iOS游戏大作 智器X7玩酷无压力

    智器X7一样可以爽玩IOS平台大作        伴随着硬件性能的不断升级与系统的完善与优化,在高端机型上流畅运行大型游戏自然不在话下,iOS的经典游戏大作不再只是独角戏,搭载智卓系统的智器X7一样可 ...

  4. iOS开发--开源库

    图像: 1.图片浏览控件MWPhotoBrowser  实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作. ...

  5. ios 关于常用的一些第三方框架的介绍

    本文转载自:http://blog.csdn.net/xiaoyuertongxue/article/details/46982879 图像:  1.图片浏览控件MWPhotoBrowser 实现了一 ...

  6. iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角)--(转)

    图像:  1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作.  ...

  7. (转)iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角)

    图像: 1.图片浏览控件MWPhotoBrowser  实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作. ...

  8. 如何在Swift中创建漂亮的iOS图表

    通过图形和图表呈现数据是当今移动应用程序最显着的特征之一.iOS图表使应用程序看起来更漂亮,更有吸引力. 在本教程中,我们将向您展示如何使用代码示例在Swift中实现我们的iOS图表.我们将看一下Sw ...

  9. iOS开发-常用第三方开源框架

    图片浏览控件MWPhotoBrowser  实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作. 下载:htt ...

最新文章

  1. 用 cooking 搭建一个简单又优雅的 Vue 项目开发环境 (入门篇)
  2. jet nano 车道识别
  3. Hat’s Words
  4. leetcode 52. N皇后 II(回溯)
  5. 现代计算机应用特点,现代计算机的特点和计算机的发展
  6. LintCode 1210. 升序子序列(DFS)
  7. 微软超级麻将AI Suphx论文发布,研发团队深度揭秘技术细节
  8. Nginx实现二级域名店铺
  9. 使用Div自动换行一事
  10. 基于matlab设计的低通滤波器
  11. Qt + 运动控制 (固高运动控制卡)【1】环境准备,框架搭建
  12. Unity 导入原神人物模型
  13. win10如何强制性关闭驱动数字签名
  14. Android分享wifi给电脑,使用Android手机共享手机网络给PC上网
  15. visio图片导入word和PPT的最清晰的方式
  16. [Leetcode] 810. Chalkboard XOR Game 解题报告
  17. 学术-物理-维空间:二维空间
  18. YTU 问题 : 数组奇偶操作
  19. [SQL Server玩转Python] 二.T-SQL查询表格值及Python实现数据分析
  20. Toad for oracle 教程

热门文章

  1. 记录一下git 打patch导入patch遇到的问题
  2. ~ 按位取反运算解析
  3. facenet 人脸识别库的搭建和使用方法(二)
  4. 美国服务器网站没有收录的原因分析
  5. 激光对射之智能变频技术
  6. 如何使用ssh来连接windows
  7. 有了解arm嵌入式主板与x86主板的优势和劣势的吗?
  8. nodeJs处理json
  9. DiskMan使用方法
  10. 测试手机的价格的软件,给大家推荐一个测心率的手机软件,不用花昂贵的价钱买设备啦~ - 薄荷减肥论坛...