投稿者「HEXADRIVE001」のアーカイブ

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

梅雨で体がべとべとするいやな季節がやってきましたね。
この時期は洗濯物の渇きが悪いので干している時間も長くなってしまい我が家の景観も悪いです。
ご無沙汰してますコンドウです。

 

今回はGame Centerシリーズ4回目です。一応前回のリンクも貼っておきます。

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

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

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

 

前回の続きである、カスタムマッチを解説していきます。
まずは前回のマッチメーキング開始が若干変更されます。

//! @param[in,out]  MatchAutoCancel     GameCenterサーバーからタイムアウトマッチングキャンセルのフラグ
@property (nonatomic) BOOL MatchAutoCancel;

// マッチメーキング開始
- (void)matchMaking:(u32)playerGroup playerAttribute:(u32)playerAttr
{
    self.MyMatch=nil;
    self.MatchMakingFlag=YES;
    self.MatchedFlag=NO;
    self.MatchAutoCancel=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をセットする)

    dispatch_async( dispatch_get_main_queue(), ^{
    [[GKMatchmaker sharedMatchmaker] findMatchForRequest:req withCompletionHandler:
     ^(GKMatch *match, NSError *error){
        self.MatchedFlag=NO;
        if(error){
            int err=[error code];
            if( err != GKErrorCancelled ){  // キャンセルした以外
                NSString *mess=[NSString stringWithFormat:@"%@ error code:%d", [error localizedDescription], err];
                NSLog(@"プレイヤの追加が出来なかった 理由:%@\n", mess);
                // エラー発生時に明示的にキャンセル発行(エラー情報が残り続ける対処)
                [[GKMatchmaker sharedMatchmaker] cancel];
            }
        }
        else if(match){ // マッチしたら
            if(match.expectedPlayerCount==0){   // マッチングがタイムアウトしてしまった
                self.MatchAutoCancel=YES;
            }
            if(self.myMatch){   //hx ここには来ないハズ
                self.myMatch=nil;
            }
            // マッチング確定
            [[GKMatchmaker sharedMatchmaker] finishMatchmakingForMatch:match];

            self.MyMatch.delegate=nil;
            self.MyMatch=match; // 保持用のプロパティを使用して対戦を保持する
            self.MyMatch.delegate=self;
        }
        self.MatchMakingFlag=NO;
    }];
    });
}

UIのデリゲート等は不要になります。(UI使わないのでそうですね)
上記の関数をマッチングするタイミングで呼び出せばです。が、1つ注意点があります
マッチングはマッチされるまで無限に行われる訳ではなく一定の時間が来たら
findMatchForRequest関数で設定しているハンドラに処理が着ます。
この時、接続人数カウンタであるmatch.expectedPlayerCountに0が入った状態で呼ばれますので
メイン処理で再度matchMaking関数を呼ぶか、はたまたユーザに問い合わせるかの対応が必要になります。
(上記のコードではMatchAutoCancelにYESが返るのでメイン処理でこれを監視すればです)

次にマッチング後の追加プレイヤー処理です。

//! 参加者追加
- (void)matchAdditional:(u32)playerGroup playerAttribute:(u32)playerAttr
{
    GKMatchRequest *req = self.MatchRequest;
    req.defaultNumberOfPlayers=2;
    req.minPlayers=2;
    req.maxPlayers=PLAYERS_MAX_PERSONS;

    req.playerGroup=playerGroup;
    req.playerAttributes=playerAttr;     // プレイヤー属性(特に指定がない場合は0をセットする)
    self.MatchMakingFlag=YES;
    self.MatchAutoCancel=NO;
    self.MatchedFlag=NO;

    dispatch_async( dispatch_get_main_queue(), ^{
    [[GKMatchmaker sharedMatchmaker] addPlayersToMatch:self.MyMatch
                                          matchRequest:req
                                     completionHandler:^(NSError *error){
        self.MatchedFlag=NO;
        if(error){
            int err=[error code];
            if( err != GKErrorCancelled ){  // キャンセルした以外
                NSString *mess=[NSString stringWithFormat:@"%@ error code:%d", [error localizedDescription], err];
                NSLog(@"Aプレイヤの追加が出来なかった 理由:%@\n", mess);
            }
            // エラー発生時に明示的にキャンセル発行(エラー情報が残り続ける対処)
            [[GKMatchmaker sharedMatchmaker] cancel];
        }
        else{
            if(self.MyMatch.expectedPlayerCount==0){    // マッチングがタイムアウトしてしまった
                self.MatchAutoCancel=YES;
            }
            [[GKMatchmaker sharedMatchmaker] finishMatchmakingForMatch:self.MyMatch];
        }
        self.MatchMakingFlag=NO;
    }];
    });
}

マッチした後の追加マッチはmatchAdditional関数を呼び出します。

なるべくユーザには「マッチングで待たせる」という流れにならないようゲーム側で工夫

(例えば2人からゲームをスタートしちゃうとか)して、 裏で追加プレイヤーの募集を続けるといい感じのアプリになります

次にmatchMakingmatchAdditionalで募集したプレイヤーがマッチした際に来るデリゲートも用意します。
今回はプレイヤーテーブルである_playersInfo._personがメイン処理とデリゲートそれぞれから呼ばれる可能性が
あるため念のために排他制御用にNSLockも追加します。

struct PersonsInfo{
    NSMutableString        _playerID;        // プレイヤ識別子(NSStringだと書き換えできないので型変更)
};

@interface GameCenter(){
    MatchingInfo           _playersInfo;
    NSLock                 *_pInfoLock;        // <-- これ追加
}

/*
 初期化関数(前回のinit関数と差し替える)
*/
-(id)init
{
    self=[super init];
    if(self){
        memset( &_playersInfo, 0, sizeof(_playersInfo) );
        [self InitializeMatchMaking];
        self.MatchRequest = [GKMatchRequest new];
        _pInfoLock= [NSLock new];                   // <-- これ追加
    }
    return self;
}

/*
 マッチング用デリゲート関数(マッチングした時/切断された時等で呼ばれる)
*/
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeConnectionState:(GKPlayerConnectionState)state
{
    if(state==GKPlayerStateUnknown){
        return;
    }
    struct MatchingInfo *person=_playersInfo._person;
    int i,cnt;
    [_pInfoLock lock];
    cnt=_playersInfo._currentPersons;
    switch(state){
    // 接続時
    case GKPlayerStateConnected:
        if(cnt >= PLAYERS_MAX_PERSONS){ // 既にマックス接続している
            break;
        }
        for(i=cnt ; i ; person++, i-- ){
            if([person->_playerID isEqualToString:playerID]){   // 既にいる
                break;
            }
        }
        if(i==0){   // リストに無かった
            person->_playerID = playerID;
            ++_playersInfo._currentPersons;
        }
        break;
    // 切断時
    case GKPlayerStateDisconnected:
        if(cnt <= 1){   // 既に誰も接続していない
            break;
        }
        for(i=1; i < cnt; i++ ){
            person++;
            if([person->_playerID isEqualToString:playerID]){   // 既にいる
                int j=PLAYERS_MAX_PERSONS-1 - i;
                if(j){  // 詰める位置?
                    person->_playerID = nil;
                    memmove(person, person+1, j * sizeof(_playersInfo._person[0]));
                }
                memset(&_playersInfo._person[PLAYERS_MAX_PERSONS-1], 0, sizeof(_playersInfo._person[0]));
                --_playersInfo._currentPersons;
                break;
            }
        }
        break;
    }
    [_pInfoLock unlock];

    // 最初の対戦ネゴシエーションを処理する
    self.MatchedFlag=YES;                           // matchイベントハンドラが呼ばれたフラグ
}

メイン処理で_playersInfoを変更する場合は必ず、

    [_pInfoLock lock];
    _playersInfoをいじる処理
    [_pInfoLock unlock];

のようにしてコールバックと競合しないようにしておきます。(読むだけならロックの必要はありません)

これでプレイヤーのマッチングは一通り終えました。あとプレイヤーの削除ですがユーザの回線切れたり、

[self.MyMatch disconnect]

が呼ばれるとmatch関数のステータスGKPlayerStateDisconnectedが着ますのでいい感じで処理しておきます。

まだいくつか足りないコールバック/デリゲート関数がありますが残りは次回に
次回は実際の通信処理に入りたいと思います
乞うご期待