HEXA BLOG

プログラム

HEXA BLOGプログラム2015.1.22

Game Centerを使ってみよう! Act.3

まだまだ寒いですね
歳のせいで新陳代謝が悪くなったのか、血行悪くてヒートテック的な保温効果の高い下着が手放せません。コンドウです。ご無沙汰です

今回も前回に引き続きiOSのGame Centerについてのお話です。

 

Game Centerを使ってみよう! Act.1

Game Centerを使ってみよう! Act.2

 

前回はGame Centerを利用する為の認証周りについて触れましたが、今回は実際の通信処理についてコード的な話をしたいと思います。

Game Centerの一番のウリじゃないかと個人的に考えているマッチング通信です

もしSock系の通信処理を経験した人なら「TCP/UDPどうなるのかな?」とか「使えるポート番号はいくらだろう?
とか「1パケットの最大長はどのくらいかな?」とか想像しますよねえ?しない?いやいや、ネットワーク通信やるなら多少なりとも気になりますよ

ここではっきりと言いますが、通信はものすごく簡単な物しか無いという事です。
具体的に言うとTCPやUDP、IPアドレス、ポート番号 そんなもの一切ありません

あるのは送信関数、受信関数(正確にはコールバックメソッド)、あとはマッチング関連の関数群ぐらいです。
通信用の関数は驚くほど無いです。無いので逆にSock系を実現しようとすると面倒だったりします
その辺りの解説は後にして、まずはマッチング処理ですねこれが無いとそもそも相互通信できませんので。

マッチング処理はちょっとだけ面倒ですので、少しずつ進めていきましょう。
マッチング処理とは通信相手となる端末との検索と通信の確立です。Game Centerでは「マッチメーク」という
表現でドキュメント等に記載されています。(※1)

まずは処理の流れです。大きく分けて

 

 APIで用意されているGUIでマッチング処理
 UIも含めて全て自前で準備する

 

の2つのパターンがあります。

まずはのマッチング方法ですがこれはGame Center関連の書籍やネットでの情報、プログラミングドキュメントでもよく見かけますね。
まずはコーディング。前回分で作ったGameCenter.hとGameCenter.mmに追記していきます。
今回はについて解説します。

typedef signed char          s8;    // 8bit 符号あり整数
typedef unsigned char        u8;    // 8bit 符号なし整数
typedef signed short        s16;    // 16bit 符号あり整数
typedef unsigned short      u16;    // 16bit 符号なし整数
typedef signed int          s32;    // 32bit 符号あり整数
typedef unsigned int        u32;    // 32bit 符号なし整数
typedef signed long long    s64;    // 64bit 符号あり整数
typedef unsigned long long  u64;    // 64bit 符号なし整数

@interface GameCenter :NSObject <GKMatchmakerViewControllerDelegate, GKMatchDelegate>
//--------------------------------------------------------
// クラスメソッド
//--------------------------------------------------------
+ (GameCenter*)instance;
//--------------------------------------------------------
// 公開メソッド
//--------------------------------------------------------
-(id)init;
@end

#define PLAYERS_MAX_PERSONS        4        // 最大同時プレイ人数(自身も含む)

struct PersonsInfo{
    NSString        _playerID;        // プレイヤ識別子
};

// マッチング情報
struct MatchingInfo{
    PersonsInfo    _person[ PLAYERS_MAX_PERSONS ];
    u32            _currentPersons;    // 接続人数(自身も含む) 1~4
};

GKMatchmakerViewControllerDelegateを基底に付けておきます。の場合は要らないところです。
GameCenter.hの記述を上記に変更します。

@interface GameCenter(){
    MatchingInfo    _playersInfo;
}
// @param[in,out]    MatchedFlag            matchイベントハンドラが呼ばれたかフラグ(NO:呼ばれていない YES:呼ばれた)
@property (nonatomic) BOOL MatchedFlag;

// @param[in,out]    MatchMakingFlag        マッチングフラグ(NO:非マッチ YES:マッチング中)
@property (nonatomic) BOOL MatchMakingFlag;

// @param[in,out]    OldMicroSecondClock    前回取得した起動からのマイクロ秒
@property (nonatomic) u64 OldMicroSecondClock;

// @param[in,out]    MatchRequest           マッチ用リクエスト
@property (retain) GKMatchRequest* MatchRequest;

// @param[in,out]    MyMatch                GKMatchオブジェクト
@property (strong) GKMatch *MyMatch;


@end

@implementation GameCenter


/*
 初期化関数(前回のinit関数と差し替える)
*/
-(id)init
{
    self=[super init];
    if(self){
        memset( &_playersInfo, 0, sizeof(_playersInfo) );
        [self InitializeMatchMaking];
        self.MatchRequest = [GKMatchRequest new];    // GKMatchRequestのインスタンスを予め取っとく
    }
    return self;
}

/*
 終了関数
*/
-(void)dealloc
{
    [self.MatchRequest release];
    [super dealloc];
}

/*
 プレイヤ情報を保持(前回の関数と差し替え)
 player        GKLocalPlayerデータ
*/
-(void)localPlayerDidChange:(GKLocalPlayer *)player
{
    self.localPlayer=player;
    if( player != nil ){
        self._playersInfo._person[0]._playerID = player.playerID;    // 自分自身のIDを配列の先頭に置く
    }
}


// マッチング処理の結果を取る通知関数です。上記の場合はキャンセルされたら処理が飛んでくるものです。
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController
{
    // キャンセルされた!
    viewController.delegate = nil;

    // controllerを閉じる
    [viewController dismissViewControllerAnimated:YES completion:^(void) {
        // アニメーション完了ハンドラ
        viewController.matchmakerDelegate = nil;
        [self InitializeMatchMaking];
        // ゲーム側にキャンセルされた事をここで通知しておく
    }];
}


// マッチング処理中に何らかのエラーが発生した場合に通知される関数です。
// エラー処理はここでゲーム側に通知しておきます。GameCenter.mmに追記します。
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error
{
    // ゲーム側にマッチング中にエラーが発生した事をここで通知しておく
}


// マッチング処理完了時に呼び出される。うまくいった場合だね!
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
{
    match.delegate = self;
    self.MyMatch = match;

    PersonsInfo* person = &_playersInfo._person[0];
    _playersInfo._currentPersons = 1;    // 少なくとも自身がいるので1からスタート
    // マッチングした自身を除く人たちの記録!
    for(NSString* playerID in match.playerIDs) {
        person++;    // [0]を飛ばすのは[0]に自身のプレイヤIDをセットしている為
        person->_playerID = playerID;    // プレイヤIDコピー
        _playersInfo._currentPersons++;
    }
    self.MatchMakingFlag = NO;
    [viewController dismissViewControllerAnimated:YES completion:^(void) {
        // アニメーション完了ハンドラ
        self.MatchedFlag = YES;
        [[GKMatchmaker sharedMatchmaker] finishMatchmakingForMatch:match];
    }];
}

/* 現在時間を取得
 戻り値:現在時間(単位:マイクロ秒)
*/
u64 GetCurrentMicroSecond()
{
    struct timeval tm;
    gettimeofday(&tm, NULL);

    u64 time = (unsigned)(tm.tv_sec) * (u64)1000000 + (unsigned)(tm.tv_usec);    // μsecを整数化
    return time;
}

<span style="color: #339966;">/*
 GameCenterに対するコマンドのタイムアウト初期化
 GameCenterサーバー死んだ時用。
*/</span>
-(void)InitializeTimeOut
{
    self.OldMicroSecondClock = GetCurrentMicroSecond();
}

/*
 GameCenterに対するコマンドのタイムアウト検出
 cnt        タイムアウト時間(単位:マイクロ秒)
 戻り値:
 true        タイムアウト
 false        正常
 GameCenterサーバー死んだ時用。
*/
-(bool)IsTimeOut:(u32)cnt
{
    u64 current = GetCurrentMicroSecond();
    u32 sa = u32(current - self.OldMicroSecondClock);
    return (sa &gt;= cnt );        // 指定時間超えたらtrue
}
@end

/*
 マッチング情報の初期化
*/
-(void)InitializeMatchMaking
{
    self.MatchMakingFlag=NO;
    self.MatchedFlag=NO;
    _playersInfo._currentPersons = 1;    // 少なくとも自身がいるので1からスタート
    self.MyMatch=nil;
}

マッチングを行った結果を返すデリゲートの設定やその他諸々設定です。

// マッチメーキング開始
- (void)matchMaking:(u32)playerGroup playerAttribute:(u32)playerAttr
{
    self.MyMatch=nil;
    self.MatchMakingFlag=YES;
    self.MatchedFlag=NO;
    [self InitializeTimeOut];            // 起動からのマイクロ秒記憶
    // マッチングの条件を設定! マッチングの条件はここでは割愛
    GKMatchRequest *req = self.MatchRequest;
    req.defaultNumberOfPlayers=2;        // デフォルト人数
    req.minPlayers=2;                    // 最少人数
    req.maxPlayers=PLAYERS_MAX_PERSONS;  // 最大人数
    req.playerGroup=playerGroup;         // プレイヤーグループ(特に指定がない場合は0をセットする)
    req.playerAttributes=playerAttr;     // プレイヤー属性(特に指定がない場合は0をセットする)
    GKMatchmakerViewController* gkmController = [[GKMatchmakerViewController alloc] initWithMatchRequest:req];
    gkmController.matchmakerDelegate = self;

    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
    // GameCenterマッチングUIの開始アニメーションをメインスレッドで実行されるメインディスパッチキューにて登録して実行
    dispatch_async( dispatch_get_main_queue(), ^{
        [delegate.rootViewController presentViewController:gkmController animated:YES completion:^(void) {
        // アニメーション完了ハンドラ
        }];
    });
    [gkmController release];
    return 0;
}

そしてマッチング開始関数です。これを呼び出せばマッチングがスタートします。結果はデリゲートで設定した関数達で得られます。

Game Centerのマッチング条件(GKMatchRequestクラス)は非常にシンプルです。

 

  • ・最小/最大人数の設定(minPlayers/maxPlayers)
  • ・プレイヤーグループ(playerGroup:符号なし32bit整数値)
  • ・プレイヤー属性(playerAttributes:符号なし32bit整数値)

 

これだけ
コンシューマ機にありがちな、マッチング用の部屋建てたり、部屋検索したりとかありません
お互いの条件が一致する者達を繋げてくれるだけです
マッチング条件は貧弱ですが、工夫次第で何とかなります

例えばプレイヤーグループですが、これはお互いに一致したプレイヤーグループ同士を繋げてくれます。(但し0を指定した場合は例外で無条件にマッチングします)
これを例えば、下位5bitをマッチング種類として32個設定、次の3bitを職業等のステータスとして設定、残りのbitでその他の条件など、全てand条件になってしまいますが1つの変数で条件を複数分けたりやり方次第で何とかなります
またプレイヤー属性はちょっとややこしく、「自身と異なる相手を探す」場合に利用します。
オセロで考えた時に、自分は黒で相手は白の人を検索したい場合

 

黒(自分) 0x0000ffff
白(相手) 0xffff0000

 

と設定します。マッチングサーバーが両者の属性を論理和(OR)した結果が0xffffffffだったらマッチする条件となる訳です。黒同士もしくは白同士は0xffffffffにならないのでマッチングしないという事です。
これもうまく活用すれば更に条件のバリエーションを増やせます。
例えば、後で追加マッチング(※2)を行う場合に未マッチングの人とマッチング済みの人たちの組み合わせのみマッチングさせたい場合などに使えますね

 

今日はここまで(力尽きました)

次回ではの自前でマッチングを解説したいと思います。このカスタムマッチはネットでも余り触れているサイトが無く書籍でも情報が本当に薄いです。(そもそもGameCenter解説本が少ない)が、このカスタムマッチのほうが断然面白いマッチングができます
例えば、

 

  • ・プレイヤーの離脱、追加ができる(今回の方法でもできなくはないですが)
  • ・マッチングするまでプレイヤーをぼけーっと待たせない(ゲーム処理の裏でマッチングすればいいのだ)

 

サーバー建てなくてもよりアクティブなネットワーク対戦が構築できます
お楽しみに

 

 

※1 Game Centerプログライミングガイド
※2 一旦マッチングした後に4人に満たない場合や、対戦人数に満たない場合に追加でマッチング募集する場合に行う機能です。例えば最初1人でもマッチングすれば即ゲーム開始し、裏で追加の募集をかける的なオペレーションだとプレイヤーも待ちのストレスを最小限に抑えることができますね。

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE 

NEWS