iOS 6 Game Center之新功能第2部分

来源:互联网 发布:淘宝如何权重 编辑:程序博客网 时间:2024/05/20 07:33


原文地址:http://www.raywenderlich.com/zh-hans/24921/ios-6-game-center%E4%B9%8B%E6%96%B0%E5%8A%9F%E8%83%BD%E7%AC%AC2%E9%83%A8%E5%88%86

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按钮时,它会发送一个分数挑战。如果你有两台设备,你就可以轻易地测试它们是否工作正常了。

挑战被成功解锁!你现在可以使用代码发送挑战了!

何去何从?

这是最终的工程​​。

如果你想要学习更多有关GameKit的内容或者其他新引入的功能,请关注我们的新书iOS 6 by Tutorials!

本书有两个完整的章节来讲述GameKit,分为如下几个主题:

  1. 新引入的类的详细说明,比如GKGameCenterViewController。
  2. 设置monkey jump游戏成就,并在游戏中通过GameKit解锁成就。
  3. 使用GameKit上传成就。
  4. 向好友发送成就挑战。
  5. 通过facebook,twitter等等社交平台分享分数和成就。
  6. 添加记录玩家在游戏中每次移动的信息的功能,之后将其和挑战一起发送。
  7. 添加一个幽灵猴子,当你试图战胜一个挑战时,它用来代表被挑战的玩家的每步移动。

所以一定要看看iOS 6 by Tutorials,因为它里边有很多很多你想要学习的内容!同时,如果你对本教程有什么疑问或者评论,欢迎参加下方的讨论区!