GameCenter対応

■概要
Apple標準のソーシャルネットワークソリューションである、GameCenterを導入してみる。
GameCenterには、スコアのLeaderboradと、実績のAchievementsと、GameMaching機能を持っている。
各々の対応しているゲームについて、世界ランキングなどが見れる仕組みとなる。

■開発の前に登録が必要
ソーシャルネットワークなので、ローカルだけで実装はできない。
開発前にiTunes Connectページから登録しておく必要がある。
Apple Developerページ右のiTunes Connectからページにアクセスし、Manage Your Applicationsで、アプリ毎に設定する。
今回は初登録なので、Add New Appを選択。
デフォルトの言語やアプリ名を入力。
Bundle IDは、ワイルドカードを指定した場合、Bundle ID Suffixを入力する必要がある。
SKU Numberは自分の好きな数字を入れていいらしいが、アルファベットも入力できる。
ただし、このアプリ設定を削除しても残るため、二度と使えないものになる。
Version Informationではジャンル選択や、詳細説明を入力できる。
Ratingは、暴力表現などを使っている場合は必要となる。とりあえずNoneで良いだろう。

Metadataには検索ワードや、サポートのメールアドレス、HPが必須入力となっている。
Uploadsでは512x512サイズのアイコンと、スクリーンショット1枚以上必須でアップロードする必要がある。
そんなこんなでアプリ1つ登録完了したが、GameCenterの登録はまだこれからだ。

■Game Centerの有効化
登録の終わったアプリから、Manage Game Centerを選択する。
初期状態ではDisenableになっているので、Enableボタンを押してやると、Leaderboardと、Achievementsが編集出来るようになる。

■Leaderborad、Achievements
まずは、Leaderboardから追加してみる。
Reference Nameは、どこに使われる文字だかわからないがとりあえずタイトル名を入れてみる。
Lerderboard IDは、後でアクセスするときのID(Category)となる。
タイムアタックゲームにするので、昇順(Low to High)を選んでおく。

Add Languageを押し、Localizationを登録する。
適当に入力してみる。Imageは512x512が必要となる。
次に、Achievementsの登録を行う。Leaderboard同様、適当に入力しておく。
ブラウザでの登録はここまでで、あとはXcodeで実装する。
 
■XcodeでGame Center登録
Xcodeでは、Gamekit.frameworkを追加する必要が有る。
ProjectのTargetのBuild Phasesの、Link Binary with Librariesから「+」を押してGamekit.frameworkを追加。
次に、得点や実績を追加するソースコードでGameKit/GameKit.hをインポートする。
アプリ起動時にでも[self authenticateLocalPlayer]を実行すると、起動時にログインされる。

GameCenterのLeaderboardを使うのに最低限必要なソースコード

#import <GameKit/GameKit.h>


-(void)authenticateLocalPlayer {

    [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {

        if (error) 

        { /* エラー処理 */ }

        else 

        { /* 認証済みユーザーを使ってハイスコアとか処理 */ }

    }];

}


-(void)submitScore:(int64_t)score forCategory:(NSString*)category {

    GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];

    scoreReporter.value = score;

    [scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {

        if (error) { /* エラー処理 */ }

    }];

}

得点を報告には[self submitScore:1234 Category:@"iSounyan"]を実行する。
Scoreには得点を、CategoryにはiTunes Connectに登録したときのLeaderboard IDを指定する。

■GameCenter画面の表示
GameCenter(GameKit.workframe)には、画面インターフェースを表示する機能が備わっているため、
アプリから呼び出してやることにする。
まずは、例としてGameKitHelperクラスを作成する。

GameKitHelper.h

#import "cocos2d.h"

#import <GameKit/GameKit.h>


@interface GameKitHelper : NSObject <GKLeaderboardViewControllerDelegate, GKAchievementViewControllerDelegate, GKMatchmakerViewControllerDelegate, GKMatchDelegate>

{

id<GameKitHelperProtocol> delegate;

bool isGameCenterAvailable;

NSError* lastError;

}

@property (nonatomic, retain) id<GameKitHelperProtocol> delegate;

@property (nonatomic, readonly) bool isGameCenterAvailable;

@property (nonatomic, readonly) NSError* lastError;


-(void) authenticateLocalPlayer;

-(void) submitScore:(int64_t)score category:(NSString*)category;

-(void) showLeaderboard;


@end

GameKitHelper.m

#import "GameKitHelper.h"


@implementation GameKitHelper

+(id) alloc

{

@synchronized(self)

{

NSAssert(instanceOfGameKitHelper == nil, @"Attempted to allocate a second instance of the singleton: GameKitHelper");

instanceOfGameKitHelper = [[super alloc] retain];

return instanceOfGameKitHelper;

}

return nil;

}


+(GameKitHelper*) sharedGameKitHelper

{

@synchronized(self)

{

if (instanceOfGameKitHelper == nil)

{

[[GameKitHelper alloc] init];

}

return instanceOfGameKitHelper;

}

return nil;

}

@synthesize delegate;

@synthesize isGameCenterAvailable;

@synthesize lastError;


-(id) init

{

if ((self = [super init]))

{

// Test for Game Center availability

Class gameKitLocalPlayerClass = NSClassFromString(@"GKLocalPlayer");

bool isLocalPlayerAvailable = (gameKitLocalPlayerClass != nil);

// Test if device is running iOS 4.1 or higher

NSString* reqSysVer = @"4.1";

NSString* currSysVer = [[UIDevice currentDevice] systemVersion];

bool isOSVer41 = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending);

isGameCenterAvailable = (isLocalPlayerAvailable && isOSVer41);

NSLog(@"GameCenter available = %@", isGameCenterAvailable ? @"YES" : @"NO");

[self registerForLocalPlayerAuthChange];

[self initCachedAchievements];

}

return self;

}


-(void) dealloc

{

CCLOG(@"dealloc %@", self);

[instanceOfGameKitHelper release];

instanceOfGameKitHelper = nil;

[lastError release];

[self saveCachedAchievements];

[cachedAchievements release];

[achievements release];

[currentMatch release];

[[NSNotificationCenter defaultCenter] removeObserver:self];

[super dealloc];

}


-(void) setLastError:(NSError*)error

{

[lastError release];

lastError = [error copy];

if (lastError)

{

NSLog(@"GameKitHelper ERROR: %@", [[lastError userInfo] description]);

}

}


-(void) authenticateLocalPlayer

{

if (isGameCenterAvailable == NO)

return;

GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];

if (localPlayer.authenticated == NO)

{

[localPlayer authenticateWithCompletionHandler:^(NSError* error)

{

[self setLastError:error];

 

if (error == nil)

{

[self initMatchInvitationHandler];

[self reportCachedAchievements];

[self loadAchievements];

}

}];

}

}


-(void) submitScore:(int64_t)score category:(NSString*)category

{

if (isGameCenterAvailable == NO)

return;

GKScore* gkScore = [[[GKScore alloc] initWithCategory:category] autorelease];

gkScore.value = score;

[gkScore reportScoreWithCompletionHandler:^(NSError* error)

{

[self setLastError:error];

 

bool success = (error == nil);

[delegate onScoresSubmitted:success];

}];

}


// Leaderboards

-(void) showLeaderboard

{

if (isGameCenterAvailable == NO)

return;

GKLeaderboardViewController* leaderboardVC = [[[GKLeaderboardViewController alloc] init] autorelease];

if (leaderboardVC != nil)

{

leaderboardVC.leaderboardDelegate = self;

[self presentViewController:leaderboardVC];

}

}


-(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController*)viewController

{

[self dismissModalViewController];

[delegate onLeaderboardViewDismissed];

}


// Achievements

-(void) showAchievements

{

if (isGameCenterAvailable == NO)

return;

GKAchievementViewController* achievementsVC = [[[GKAchievementViewController alloc] init] autorelease];

if (achievementsVC != nil)

{

achievementsVC.achievementDelegate = self;

[self presentViewController:achievementsVC];

}

}


-(void) achievementViewControllerDidFinish:(GKAchievementViewController*)viewController

{

[self dismissModalViewController];

[delegate onAchievementsViewDismissed];

}

cocos2dから呼ぶには更に、AppDelegate.mにも追記する必要がある。
下記の、「//GameCenter対応」行を追記する。

AppDelegate.m 98行目付近

// make the View Controller a child of the main window

[window addSubview: viewController.view];

window.rootViewController = viewController; //GameCenter対応

[window makeKeyAndVisible];

で、画面を出す時は、下記のようにコーディングすることで画面遷移する。

Leaderboard画面の表示

GameKitHelper *gkHelper = [GameKitHelper sharedGameKitHelper];

[gkHelper showLeaderboard];

遷移した画面は正式なGameCenterではなく、SANDBOXと呼ばれる画面になる。
SANDBOXはテスト用のモードと考えれば良い。>GameCenterのSANDBOX記事も参照
Leaderboard IDを変更して各ステージのタイムを記録してみたが、どうやらなんらかのデータは記録されているようだが(左画像)、
見てみると(右画像)、なぜかNo Scoresになっている。
これは、GameCenterのSandboxでは新しいアカウントを絶対に作成しなければならないからだ。既存のIDは使えない。
新しいメアドでアカウントを設定してやると表示されるようになった。謎仕様である。
なお、Scoreに1234を入れると、12.34になる。secの文字はiTunes ConnectのLeaderboradの設定で単位を変更する事が出来る。