文章预读
Game Center Configuration Guide for iTunes Connect
iOS开发长文--通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总
请看以上文章的GameCenter部分
Game Center是由苹果发布的在线多人游戏社交网络,通过它游戏玩家可以邀请好友进行多人游戏,它也会记录玩家的成绩并在排行榜中展示,同时玩家每经过一定的阶段会获得不同的成就。这里就简单介绍一下如何在自己的应用中集成Game Center服务来让用户获得积分、成就以及查看游戏排行和已获得成就。
准备工作:
①首先要创建一个Explicit App ID,不能创建Wildcard App ID,然后默认就勾选了GameCenter这个功能
②然后创建描述文件
③然后在ITC中创建刚刚那个BundleID的应用,这个应用可以不用提交
④创建沙盒测试用户,这个应该是iOS9.0还是iOS10.0之前的,现在是不需要添加沙盒测试用户了。如果要看测试数据,还需要到手机设置==>GameCenter==>sandbox
⑤在ITC应用的Features里面找到GameCenter,然后配置GameCenter
以下是排行榜的配置:其中重要的是排行榜ID,项目中要配置
⑥接下来就是工程中的配置了,第一步先打开Capability这个功能
⑦接下来就是添加代码了:在需要的位置导入:
#import <GameKit/GameKit.h>
//验证授权
-(void)authPlayer{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController * __nullable viewController, NSError * __nullable error){
if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
NSLog(@"%@",@"已经授权!");
}else if(viewController){
[self presentViewController:viewController animated:YES completion:nil];
}else{
if (!error) {
NSLog(@"%@",@"授权OK");
} else {
NSLog(@"没有授权");
NSLog(@"AuthPlayer error :%@",error);
}
}
};
}
// 上传分数给 gameCenter
-(void)saveHighScore{
if ([GKLocalPlayer localPlayer].isAuthenticated) {
//得到分数的报告
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier:@"你的排行榜ID,ITC中找"];
scoreReporter.value = 1000;
NSArray<GKScore*> *scoreArray = @[scoreReporter];
//上传分数
[GKScore reportScores:scoreArray withCompletionHandler:nil];
}
}
//下载 game center 某一排行榜中的分数及排名情况
- (void)downLoadGameCenter{
if ([GKLocalPlayer localPlayer].isAuthenticated == NO) {
NSLog(@"没有授权,无法获取更多信息");
return;
}
GKLeaderboard *leaderboadRequest = [GKLeaderboard new];
//设置好友的范围
leaderboadRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
//指定那个区域的排行榜
NSString *type = @"today";
if ([type isEqualToString:type]) {
leaderboadRequest.timeScope = GKLeaderboardTimeScopeToday;
}else if([type isEqualToString:@"week"]){
leaderboadRequest.timeScope = GKLeaderboardTimeScopeWeek;
}else if([type isEqualToString:@"all"]){
leaderboadRequest.timeScope = GKLeaderboardTimeScopeAllTime;
}
//哪一个排行榜
NSString *ID = @"你的排行榜ID,ITC中找";
leaderboadRequest.identifier = ID;
//从那个排名到那个排名
NSInteger location = 1;
NSInteger length = 10;
leaderboadRequest.range = NSMakeRange(location, length);
//请求数据
[leaderboadRequest loadScoresWithCompletionHandler:^(NSArray<GKScore *> * _Nullable scores, NSError * _Nullable error) {
if (error) {
NSLog(@"请求分数失败");
NSLog(@"error = %@",error);
}else{
NSLog(@"请求分数成功");
//定义一个可变字符串存放用户信息
NSMutableString *userInfo = [NSMutableString string];
NSString *rankBoardID = nil;
for (GKScore *score in scores) {
NSLog(@"");
//得到排行榜的 id
NSString *gamecenterID = score.leaderboardIdentifier;
NSString *playerName = score.player.displayName;
NSInteger scroeNumb = score.value;
NSInteger rank = score.rank;
NSLog(@"排行榜 = %@,玩家名字 = %@,玩家分数 = %zd,玩家排名 = %zd",gamecenterID,playerName,scroeNumb,rank);
[userInfo appendString:[NSString stringWithFormat:@"玩家名字 = %@,玩家分数 = %zd,玩家排名 = %zd",playerName,scroeNumb,rank]];
[userInfo appendString:@"\n"];
rankBoardID = gamecenterID;
}
//弹框展示
[self popShowViewWithTitileName:[NSString stringWithFormat:@"%@ 排行榜的信息",rankBoardID] andInfo:userInfo];
}
}];
}
- (void)getAllOnlineFriends {
if ([GKLocalPlayer localPlayer].isAuthenticated == NO) {
NSLog(@"没有授权,无法获取好友信息");
return;
}
[[GKLocalPlayer localPlayer] loadFriendPlayersWithCompletionHandler:^(NSArray<GKPlayer *> * _Nullable friendPlayers, NSError * _Nullable error) {
//定义一个可变字符串存放用户信息
NSMutableString *userInfo = [NSMutableString string];
for (GKPlayer *player in friendPlayers) {
NSString *name = player.displayName;
NSString *al = player.alias;
//NSString *ID = player.guestIdentifier;
NSString *ID = @"";
[userInfo appendString:[NSString stringWithFormat:@"好友名字 = %@,nickName = %@%@",name,al,ID]];
[userInfo appendString:@"\n"];
}
[self popShowViewWithTitileName:@"好友信息" andInfo:userInfo];
}];
}
//遵循代理
@interface ViewController ()<GKGameCenterControllerDelegate>
//显示排行榜 可以跳转到自定的 game 排行榜 和 跳转到那个时间段
- (void)gameCenter{
if ([GKLocalPlayer localPlayer].isAuthenticated == NO) {
NSLog(@"没有授权,无法获取展示中心");
return;
}
UIViewController *vc = [self.view.window rootViewController];
GKGameCenterViewController *GCVC = [GKGameCenterViewController new];
//跳转指定的排行榜中
[GCVC setLeaderboardIdentifier:@"你的排行榜ID"];
//跳转到那个时间段
NSString *type = @"all";
if ([type isEqualToString:@"today"]) {
[GCVC setLeaderboardTimeScope:GKLeaderboardTimeScopeToday];
}else if([type isEqualToString:@"week"]){
[GCVC setLeaderboardTimeScope:GKLeaderboardTimeScopeWeek];
}else if ([type isEqualToString:@"all"]){
[GCVC setLeaderboardTimeScope:GKLeaderboardTimeScopeAllTime];
}
GCVC.gameCenterDelegate = self;
[vc presentViewController:GCVC animated:YES completion:nil];
}
//实现代理:
#pragma mark - GKGameCenterControllerDelegate
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
// 上传成就
- (void)uploadAchievment {
if ([GKLocalPlayer localPlayer].isAuthenticated == NO) {
NSLog(@"没有授权,上传不了成就");
return;
}
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:@"challenge1"];
[achievement setPercentComplete:10];
[GKAchievement reportAchievements:@[achievement] withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error);
}else{
NSLog(@"上传成就成功");
}
}];
}
//下载 game center 中的所有排行榜的榜单内容,但是只是显示了每一个排行榜的内容,可以拼接下做一个TableView来显示
- (void)downloadGameCenter {
if ([GKLocalPlayer localPlayer].isAuthenticated == NO) {
NSLog(@"没有授权,无法获取更多信息");
return;
}
[GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray<GKLeaderboard *> * _Nullable leaderboards, NSError * _Nullable error) {
//定义可变字符串
NSMutableString *leadBoardInfo = [NSMutableString string];
for (GKLeaderboard *lb in leaderboards) {
NSString *ID = lb.identifier;
NSString *leadBoardTittle = lb.title;
[lb loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
NSMutableString *stringM = [NSMutableString string];
if (!error) {
for (NSObject *score in scores) {
GKScore *newScore = (GKScore *)score;
GKPlayer *player = newScore.player;
NSString *playerID = player.playerID;
NSString *alias = player.alias;
NSString *displayName = player.displayName;
NSString *guestId = player.guestIdentifier;
NSInteger rank = newScore.rank;
NSDate *date = newScore.date;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss zzz"];
NSString *currentDateString = [dateFormatter stringFromDate:date];
NSString *leaderboardID = newScore.leaderboardIdentifier;
NSInteger value = newScore.value;
NSInteger formattedValue = newScore.formattedValue;
NSLog(@"score = %@",score);
[stringM appendFormat:@"\nLeaderboardID:%@, PlayerID:%@, NickName:%@, DisplayName:%@, GuestID:%@, Rank:%zd, Value:%zd, Time:%@",leaderboardID,playerID,alias,displayName,guestId,rank,value,currentDateString];
}
}
[self popShowViewWithTitileName:leadBoardTittle andInfo:stringM];
}];
[leadBoardInfo appendString:[NSString stringWithFormat:@"排行榜id = %@,排行榜标题 = %@",ID,leadBoardTittle]];
[leadBoardInfo appendString:@"\n"];
}
//[self popShowViewWithTitileName:@"所有的排行榜" andInfo:leadBoardInfo];
}];
}
//Private method 弹框
-(void)popShowViewWithTitileName:(NSString *)tittleName andInfo:(NSString*)info{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:tittleName message:info preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:true completion:nil];
}
所需参数
①具备GameCenter功能的描述文件
②成就ID和排行榜ID
注意问题
①看下手机的版本,如果是路径:设置==>GameCenter==>Sandbox能找到的话那么我们就需要开启这个Sandbox,如果路径没有表示的是新系统的,就不需要了。
②我们在测试的功能的时候都需要先授权,但是授权一次就OK,然后才能测试出功能
③如果我们测试的时候用两台设备测试,但是我们查看排行榜的数据的时候只能看到自己设备的数据,那可能是由于数据还没有统计过来,需要一点时延,可能几个小时后数据就能同步了,放心妥妥的睡一觉,明天第二天再来测试就能显示两台设备跑的数据了...
④另外如果发布的时候是需要勾选GameCenter开关的,见下图:
调试过程中报错解决:
①问题一
报错如下:
Error Domain=GKErrorDomain Code=15 "未能完成所请求的操作,因为 Game Center 未识别此应用程序。"
问题解决:
①工程中开启Capability这个功能项
②对比下BundleId和ITC后台的BundleID是否一致
③再检查看itunesconnect里是不是没有添加过排行榜或者成就设置,必须要至少添加一条
④设置里面开启GameCenter.
②问题二:
报错如下:
[Error] _authenticateUsingAlert:Faied to authenticate player with existing credentials.
Error: Error Domain=GKErrorDomain Code=6 "未能完成所请求的操作,因为本地玩家尚未通过认证。"
UserInfo={NSLocalizedDescription=未能完成所请求的操作,因为本地玩家尚未通过认证。
原因:这是由于GameCenter进入后台关闭导致的...
解决办法:再次进入后台,再进入前台,配合验证bolck中的代码如果viewController有值那么就展示此viewController就会出现系统的输入GameCenter id 的界面,下面有“注意注意”中有截图,所需配合的代码如下:
if(viewController){
//这个是GameCenter没有开启的时候
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
}
问题参考:
Unity iOS Game Center帐号验证(包括python后端)
解决 Error Domain=GKErrorDomain Code=15 "无法完成所请求的操作,
文章参考:
iOS Game Center 登陆验证实现
IOS: How to authenticate the GKLocalPlayer on my 'third party server'.
服务器端验证 Apple Game Center GKLocalPlayer 签名(PHP描述)
以上!!!
注意注意:
GameCenter的验证流程:
①GameCenter通过以下方法验证登录:
[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController * __nullable viewController, NSError * __nullable error){
//code here...
}
这个方法内部通过判断error和viewController来判定是否弹出登录框,如果viewController的值为nil,那么表明GameCenter的开关并没有关,是通过验证的,如下图:
如果viewController不为nil,那么表明GameCenter的开关是关闭的,如下图:
如果error为nil,并且[[GKLocalPlayer localPlayer] isAuthenticated]值为YES的话,一般就是验证成功的.
具体的验证方法如下:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
//NSLog(@"localPlayer %@",localPlayer);
localPlayer.authenticateHandler = ^(UIViewController * __nullable viewController, NSError * __nullable error){
if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
NSLog(@"%@",@"已经授权!");
//这个是在本地服务器上验证登录信息的,具体参考本小节结束给出
[[GKLocalPlayer localPlayer] generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
if (error) {
NSLog(@"ERROR: %@",error);
}
}];
if (localPlayer.playerID && localPlayer.playerID.length) {
[KODGCPlayerInfo defaultUserInfo].playerID = [GKLocalPlayer localPlayer].playerID;
[KODGCPlayerInfo defaultUserInfo].isFirstAuthenticationFlag = NO;
}
}else if(viewController){
//这个是GameCenter没有开启的时候
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
}else{
if (!error) {
NSLog(@"%@",@"授权OK");
} else {
NSLog(@"没有授权");
NSLog(@"AuthPlayer error :%@",error);
}
}
};
②因为GKLocalPlayer是单例,并且苹果在内部把这个验证的block只传一次,但是会多次调用这个block信息,那么在何时会调用这个信息呢,请看以下说明:
1>程序一启动,只要点击登录按钮传入这个验证block,就会开始进行验证,然后如果验证成功,那么就会在屏幕上方弹出一个信息条,显示某某GameCenter账号已经登录了...
2>只要在程序中你再次点击这个登录按钮再次传入这个验证block是不会再进行验证的,因为你这个账号一直在使用没有问题...
3>但是如果我们让此程序进入后台再回到前台,那么传入的验证block将再次被调用。即每进入一次后台再回到前台就会调用一次验证block.
4>如果我们在进入后台的时候将GameCenter开关给关掉了,那么再回到前台的时候调用验证block,打印错误信息,但是此时的ViewController为nil,错误信息如下:
[Error] _authenticateUsingAlert:Faied to authenticate player with existing credentials.
Error: Error Domain=GKErrorDomain Code=6 "未能完成所请求的操作,因为本地玩家尚未通过认证。"
UserInfo={NSLocalizedDescription=未能完成所请求的操作,因为本地玩家尚未通过认证。
如果此时你再次进入后台,什么都不做,再次回到前台,那么会弹出GameCenter输入账号系统的验证,如下图:
5>如果我们进入后台,直接将GameCenter关掉,然后再开启GameCenter再验证另外一个Apple ID,再次回到前台,此时会在界面上出现第二个账号登录的横幅弹框。
对接游戏的登录系统
我们只需要每次调用验证block的时候,如果验证是成功的,并且localPlayer的playerID是有值的,那么我就记录这个localPlayer,然后保持最新的,如果用户点击登录,那么直接用记录的那个localPlayer去登录就行了,因为如果用户的行为是自己主动去后台切换了那个GameCenter的ID,那么意味着用户要切换GameCenter的ID从而来切换游戏里面的登录角色等等,那么此时用户再次进入游戏的时候,就应该主动去找到切换登录的按钮,再次点击登录即可,那么此时登录的账户就是最新的localPlayer了。