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];}
以上代码有很强的自说明性,不过还是按步骤解释一下:
- 创建一个名为playerIds的数组用来存储本地玩家所有好友的ID。
- 然后此方法开始遍历返回的分数。
- 对每一个分数,都在data source中创建相应的数据,并且在playerIds数组中保存player ID。
- 如果这个分数比本地玩家的分数低,该分数对应的在data source中的数据会被标记。
- 分数被保存在data source字典中。
- 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; }}
以下是上面方法的详细步骤分解:
- 此方法首先检查玩家是否输入了消息。如果没有,就把challengeTextField的边框设为红色。
- 如果用户输入了文本,此方法择查找所有被选中的玩家ID,并把它们保存到playerIds数组中。
- 如果用户选择了一个火一个以上的玩家挑战的话,则使用玩家ID作为参数调用GameKitHelper的sendScoreChallengeToPlayers:withScore:方法。此方法会发送挑战给所有已选择的玩家。
编译并运行游戏。现在当你点击FriendsPickerViewController界面的Challenge Friends按钮时,它会发送一个分数挑战。如果你有两台设备,你就可以轻易地测试它们是否工作正常了。
挑战被成功解锁!你现在可以使用代码发送挑战了!
何去何从?
这是最终的工程。
如果你想要学习更多有关GameKit的内容或者其他新引入的功能,请关注我们的新书iOS 6 by Tutorials!
本书有两个完整的章节来讲述GameKit,分为如下几个主题:
- 新引入的类的详细说明,比如GKGameCenterViewController。
- 设置monkey jump游戏成就,并在游戏中通过GameKit解锁成就。
- 使用GameKit上传成就。
- 向好友发送成就挑战。
- 通过facebook,twitter等等社交平台分享分数和成就。
- 添加记录玩家在游戏中每次移动的信息的功能,之后将其和挑战一起发送。
- 添加一个幽灵猴子,当你试图战胜一个挑战时,它用来代表被挑战的玩家的每步移动。
所以一定要看看iOS 6 by Tutorials,因为它里边有很多很多你想要学习的内容!同时,如果你对本教程有什么疑问或者评论,欢迎参加下方的讨论区!
- iOS 6 Game Center之新功能第2部分
- iOS 6 Game Center之新功能第1部分
- iOS游戏开发之Game Center研究
- iOS游戏开发之Game Center研究
- ios Game-Center 开发 part1
- ios Game-Center 开发 part2
- 2dx Game Center
- 通过 Health Center API 监视 Java 应用程序,第 2 部分
- 通过实例学习 NetBeans 6 的新功能 第 2 部分:Java 桌面应用程序
- Java Math 类中的新功能,第 2 部分 浮点数
- 2dx game center 简介
- 教程:Corona集成Game Center(iOS)
- iOS - 集成game center (leader board)
- iOS Game Center 登陆验证实现
- iOS部分-UI基础控件 - 01天 入门 第10课 frame&bounds¢er属性
- Game Center
- App ID & Game Center - II (代码部分)
- iOS 6的新功能
- 自动生成的 菜单程序,一会分析一下
- iOS 6 Game Center之新功能第1部分
- android安装时遇到的问题
- Openfiler 配置 NFS 示例
- “.rodata.str1.4”的连接(link)问题
- iOS 6 Game Center之新功能第2部分
- Oracle数据库中保存文件(C#)
- Qt creator 问题 during startup program exited with code 0x0的折中解决
- 高手博客列表
- CKEDITOR 中<p>标签引发的问题
- C++著名程序库的比较和学习经验
- Objective-c 的 @property 详解
- AsyncTask的使用
- 打包python程序为exe文件using cx_freeze