Game Center

来源:互联网 发布:mysql虚拟主机 编辑:程序博客网 时间:2024/05/16 17:24

你也许曾听说过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)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];        }     }];}

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

  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)_distance        category: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*)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 ()        <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 {    //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];}

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

  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    [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;    }}

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

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

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

0 0