iOS开发之GameCenter使用

文章预读

Game Center Configuration Guide for iTunes Connect

iOS游戏开发之Game Center实战

iOS开发长文--通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总

请看以上文章的GameCenter部分

Game Center是由苹果发布的在线多人游戏社交网络,通过它游戏玩家可以邀请好友进行多人游戏,它也会记录玩家的成绩并在排行榜中展示,同时玩家每经过一定的阶段会获得不同的成就。这里就简单介绍一下如何在自己的应用中集成Game Center服务来让用户获得积分、成就以及查看游戏排行和已获得成就。

准备工作:

①首先要创建一个Explicit App ID,不能创建Wildcard App ID,然后默认就勾选了GameCenter这个功能
②然后创建描述文件
③然后在ITC中创建刚刚那个BundleID的应用,这个应用可以不用提交
④创建沙盒测试用户,这个应该是iOS9.0还是iOS10.0之前的,现在是不需要添加沙盒测试用户了。如果要看测试数据,还需要到手机设置==>GameCenter==>sandbox


Snip20180312_22.png

⑤在ITC应用的Features里面找到GameCenter,然后配置GameCenter


Snip20180312_16.png

以下是排行榜的配置:其中重要的是排行榜ID,项目中要配置


Snip20180312_19.png

Snip20180312_20.png

⑥接下来就是工程中的配置了,第一步先打开Capability这个功能


Snip20180312_24.png

⑦接下来就是添加代码了:在需要的位置导入:

#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开关的,见下图:


Snip20180328_40.png

调试过程中报错解决:
①问题一
报错如下:

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 &quot;无法完成所请求的操作,

文章参考:
iOS Game Center 登陆验证实现
IOS: How to authenticate the GKLocalPlayer on my 'third party server'.
服务器端验证 Apple Game Center GKLocalPlayer 签名(PHP描述)

蓝瘦.gif

以上!!!











注意注意:


GameCenter的验证流程:

①GameCenter通过以下方法验证登录:

[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController * __nullable viewController, NSError * __nullable error){
//code here...
}

这个方法内部通过判断error和viewController来判定是否弹出登录框,如果viewController的值为nil,那么表明GameCenter的开关并没有关,是通过验证的,如下图:


Snip20180330_42.png

如果viewController不为nil,那么表明GameCenter的开关是关闭的,如下图:


Snip20180330_43.png

如果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输入账号系统的验证,如下图:


Snip20180330_45.png

5>如果我们进入后台,直接将GameCenter关掉,然后再开启GameCenter再验证另外一个Apple ID,再次回到前台,此时会在界面上出现第二个账号登录的横幅弹框。


Snip20180330_46.png

对接游戏的登录系统

我们只需要每次调用验证block的时候,如果验证是成功的,并且localPlayer的playerID是有值的,那么我就记录这个localPlayer,然后保持最新的,如果用户点击登录,那么直接用记录的那个localPlayer去登录就行了,因为如果用户的行为是自己主动去后台切换了那个GameCenter的ID,那么意味着用户要切换GameCenter的ID从而来切换游戏里面的登录角色等等,那么此时用户再次进入游戏的时候,就应该主动去找到切换登录的按钮,再次点击登录即可,那么此时登录的账户就是最新的localPlayer了。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342