夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【iPhoneアプリ、cocos2d】アプリをGame Center対応にする方法(後編: プログラミング編)

cocos2dで開発したiPhoneアプリをGame Center対応にする方法の後編です。
前編はこちら
前編でiTunes Connectでの設定を終えました。後編の今回はGame CenterのLeaderboard機能を実装するプログラミングを行います。

1. Info.plistファイルの編集
プロジェクトのInfo.plistファイルにgamekit対応のデバイスが必須である事を記述します。
Info.plistのRequired device capabilitiesを開き、+ボタンを押すとNew itemという行が生成されるので、Keyをgamekitに変更します。TypeはBoolean, ValueはYesにします。

f:id:takujidev:20130903210617j:plain

2. ヘッダーファイルの編集
ゲームセンターを組み込むクラスのヘッダファイルにGameKit/GameKit.hをインポートし、GKLeaderboardViewControllerDelegateプロトコルを採用する事を宣言します。

#import "cocos2d.h"
#import <GameKit/GameKit.h>


@interface TitleScene : CCLayer <GKLeaderboardViewControllerDelegate>
{
}

+(CCScene *) scene;

@end

3. ゲームセンターの画面を表示するビューコントローラーを用意します。
cocos2dのゲームにGame Centerの画面を表示するにはUIKitのビューコントローラーを用意します。cocos2dのゲームだけやっているとUIKitとかビューコントローラーとかなじみが薄くてきちんと理解できていませんが、とりあえずTwitterの組み込みのときに作ったものをGame Centerにも共用します。

宣言と初期化はこんな感じにやっています。
初期化をonEnterでやっているのは元々このクラスがcocos2dのIntroLayerクラスだったからです。
addSubviewでビューコントローラーをcocos2dの画面に表示できるようにします。

@implementation TitleScene
{

    UIViewController* _viewController;
}

- (void)onEnter
{
    [super onEnter];
    _viewController = [[UIViewController alloc] init];
    [[[CCDirector sharedDirector] view] addSubview:_viewController.view];
}

4. ゲームセンターの画面を表示する処理
まずLeaderboardのビューコントローラーを作ります。
デリゲートとかよくわからないのがまた出てきましたが、ゲームコントローラーの処理が完了するときに自分が呼ばれるようselfをleaderboardDelegateに設定します。

timeScopeにGKLeaderboardTimeScopeAllTime設定すると、ゲームセンターを開いたときにまず全期間(すべて)のランキングが表示されます。今日のランキングを表示する場合はAllTimeの部分をToday、週間ランキングを表示する場合はWeekにします。

categoryにはiTunesConnectに設定したLeaderboadのIDを入れます。ここでは例としてBeeCluster.easyを入れていますが、実際のプログラムでは、現在の難易度設定のリーダーボードを読むようにしています。
_viewControllerに対しpresentViewControllerメッセージでリーダーボードのビューコントローラーを表示するよう指示します。

        GKLeaderboardViewController* leaderboardController = [[GKLeaderboardViewController alloc] init];
        if (leaderboardController != nil){
            leaderboardController.leaderboardDelegate = self;
            leaderboardController.timeScope = GKLeaderboardTimeScopeAllTime; //全期間のハイスコアを表示
            leaderboardController.category = @"BeeCluster.easy";
            [_viewController presentViewController: leaderboardController animated: YES completion:nil];
        }

5. ゲームセンターが終了したときの処理
ユーザーがゲームセンターの画面の完了ボタンを押すとleaderboardVewControllerDidFinishメソッドが呼ばれます(先ほどdelegateにselfを指定したので)。ここでは、_vewControllerに対し先ほどpresentViewControllerで表示したリーダーボードのビューコントローラーを消すよう指示します。

- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
    [_viewController dismissViewControllerAnimated:YES completion:nil];
}

6. Leaderboadとのスコアデータのやり取りの処理
リーダーボードを表示できるようになったので、データをアップロード、ダウンロードできるようにします。
次の例では、NSUserDefaultsに記録したローカルのハイスコアデータをゲームセンターのリーダーボードから読み出したハイスコアデータと比較し、スコアが高い方に合わせる処理をしています。

ゲームセンターにアクセスするには[GKLocalPlayer localPlayer].authenticatedをチェックしてプレイヤーがゲームセンターにログインしている事を確認します。ログインしていない場合は何もしません。

このプログラムでは自分のスコアだけを読み出せば良いので、自分のplayerIDだけを入れたNSArrayを作ります。このアレイに入っているプレイヤーについてGKLeaderboadのインスタンスを作り、ゲームセンターのリーダーボードからスコアのデータを読み出します。
どういう検索方法でデータを読み出すかはプロパティで指定します。timeScope, categoryはLeaderboadの表示と同様です。rangeはどの範囲のデータを読み出すかの指定です。
(initiWithPlayerIDsで初期化した場合は無視されるらしいです。なのでここで設定しても意味はないようです。)

検索条件を設定したらloadScoresWithCompletionHandlerでデータを読み出します。
データの読み出しには時間が掛かるので、ここで処理は一旦アプリ側に戻ります。データの受信が完了したらブロック(if (error != nil){の行)から処理が再開します。エラーはとりあえず無視します。

リーダーボードから読み出したスコアはscoresアレイに入っているので、アレイがnilでなければ最初のインデックスのデータを読み出しハイスコアとします。

注意しなければならないのはnilが帰ってきたときの処理です。初めてプレイするときゲームセンターのデータはまっさらなのでscoresアレイはnilとなります。これをどう扱うかが問題です。このプログラムでは0と考え以降の処理を進めます。
(失敗談:はじめのバージョンではnilをエラーと考え以降の処理を全てスキップしたため誰もスコアをアップロードできず、慌てて修正したアプリを審査に出すはめになりました。コーディング時はLeaderboardにデータがある状態で試行錯誤してそういうコードにしてしまったので、データがまっさらな状態の動作で誤動作する事に気づきませんでした。今考えると、別のゲームセンターアカウントを作ってテストすべきでした。)

NSUserDefaultから読み出したハイスコアと比較し、Leaderboardの方がスコアが高ければNSUserDefaultの方をそれに合わせて書き換えます。この状況はアプリを消してから再インストールすると発生します。

NSUserDefaultのスコアの方が高い場合はGame CenterLeaderboardの方を合わせます。GKScoreオブジェクトに値を設定し、reportScoreWithCompletionHanderでデータをアップロードします。

            _highScoreEasy = [[NSUserDefaults standardUserDefaults] integerForKey:@"highScoreEasy"];
            if ([GKLocalPlayer localPlayer].authenticated) { //ゲームセンターにログイン中かチェック
                NSArray* playerID = [[NSArray alloc] initWithObjects:[GKLocalPlayer localPlayer].playerID, nil]; //自分のプレイヤーIDのみのArrayを用意
                GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:playerID];
                if (leaderboardRequest != nil) {
                    leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
                    leaderboardRequest.category = @"BeeCluster.easy";
                    leaderboardRequest.range = NSMakeRange(1,1); //一番良いスコアを読み出す
                    // EASYのハイスコアをGame Centerから読み出す
                    [leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
                        if (error != nil) {
                        }
                        NSInteger highScore;
                        if (scores != nil) {
                            highScore = ((GKScore *)[scores objectAtIndex:0]).value;
                        } else {
                            // 初回プレイ時はscoresがnilなので0ハイスコアを0とする。
                            highScore = 0;
                        }
                        if (highScore > _highScoreEasy) { // Game Centerのスコアが高い場合NSUserDefaultsをそれに合わせる
                            _highScoreEasy = highScore;
                            [[NSUserDefaults standardUserDefaults] setInteger:highScore forKey:@"highScoreEasy"];
                        } else { //NSUserDefaultsのスコアが高い場合GameCenterをそれに合わせる
                            GKScore *scoreReporter = [[GKScore alloc] initWithCategory:@"BeeCluster.easy"];
                            scoreReporter.value = _highScoreEasy;
                            scoreReporter.context = 0;
                            [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
                            }];
                        }
                    }];
                }
            }
            break;

アプリのリリース前ははサンドボックスと呼ばれる特殊な環境のゲームセンターが呼び出されます。
通常のアカウントではログインできないのでサンドボックス用にテストアカウントを作成する必要があります。

Game Centerプログラミングについての詳細はこちらのアップルの公式ドキュメントをご参照ください。
https://itunes.apple.com/jp/app/beecluster-wu-liaono-zongsukurorushutingugemu/id663801586?mt=8&uo=4&at=10laCt