iOS GameCenter 挑战,排名

你也许曾听说过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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值