iOS 上的游戏中心。如何避免首局结束后的消息发送问题?

Game Center on iOS. How to avoid message-sending problems after the first game ends?

这是我第一次在我的游戏中实现匹配。我正在使用 cocos2d-x v3.x 框架。该游戏是用 C++ 编写的,我也在利用 Apple 的 GameKit 框架 (Objective-C)。

我从一开始就在关注 Ray Wenderlich 的教程:http://tinyurl.com/j8uoftg

我想知道是否有人遇到过与我遇到的相同问题。我将在下面展示主要问题。任何帮助或建议将不胜感激...

首先,我在 cocos2d-x (MultiPlayer.mm) 的 init 上设置了配对:

bool MultiPlayer::init() {
    // super init first
    if (!Layer::init()) {return false;}

    //...

    [[MultiPlayerLayer sharedManager] setUpGame];
}

内部MultiPlayerLayer.mm...

-(id) init {
    if( (self=[super init])) {}

    return self;
}

- (void)setUpGame {

    isPlayer1 = YES;

    AppController *delegate = (AppController *) [UIApplication sharedApplication].delegate;
    [[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController:delegate.viewController delegate:self];

    ourRandom = arc4random();
    [self setGameState:kGameStateWaitingForMatch];
}

Game Center 端一切正常。系统提示我现在开始游戏,Game Center 会查找玩家。找到玩家后,通知代表可以开始比赛 (GCHelper.m):

- (void)lookupPlayers {
// a few lines later...

matchStarted = YES;
[delegate matchStarted];
}

现在我们回到 MultiPlayerLayer.mm...

- (void)matchStarted {
    printf("Match started\n\n");
    if (receivedRandom) {
        [self setGameState:kGameStateWaitingForStart];
    } else {
        [self setGameState:kGameStateWaitingForRandomNumber];
    }
    [self sendRandomNumber];
    [self tryStartGame];
}

当发送 sendRandomNumber 消息时,我们通过生成一个随机数(顺便说一下,这是有效的)来确定谁是玩家 1 和玩家 2:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {

    // Store away other player ID for later
    if (otherPlayerID == nil) {
        otherPlayerID = [playerID retain];
    }

    Message *message = (Message *) [data bytes];

    if (message->messageType == kMessageTypeRandomNumber) {

        MessageRandomNumber * messageInit = (MessageRandomNumber *) [data bytes];
        printf("Received random number: %ud, ours %ud\n\n", messageInit->randomNumber, ourRandom);
        bool tie = false;

        if (messageInit->randomNumber == ourRandom) {
            printf("TIE!\n\n");
            tie = true;
            ourRandom = arc4random();
            [self sendRandomNumber];
        } else if (ourRandom > messageInit->randomNumber) {
            printf("We are player 1\n\n");
            isPlayer1 = YES;
        } else {
            printf("We are player 2\n\n");
            isPlayer1 = NO;
        }

        if (!tie) {
            receivedRandom = YES;
            if (gameState == kGameStateWaitingForRandomNumber) {
                [self setGameState:kGameStateWaitingForStart];
            }
            [self tryStartGame];
        }

    }
// ...
}

然后我们尝试开始游戏。因此,如果我是玩家 1 并正在等待,我们会将游戏状态设置为活动状态,设置字符串(获取其他玩家 ID)并向另一方发送消息以开始游戏:

- (void)tryStartGame {
    if (isPlayer1 && gameState == kGameStateWaitingForStart) {
        [self setGameState:kGameStateActive];
        [self sendGameBegin];
        [self setupStringsWithOtherPlayerId:otherPlayerID];
    }

}

发送 sendGameBegin 消息后,我们将另一端的状态设置为活动并设置玩家 ID 字符串:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {

    // Store away other player ID for later
    if (otherPlayerID == nil) {
        otherPlayerID = [playerID retain];
    }

    Message *message = (Message *) [data bytes];

    if (message->messageType == kMessageTypeRandomNumber) {

        // ...

    } else if (message->messageType == kMessageTypeGameBegin) {

        [self setGameState:kGameStateActive];
        [self setupStringsWithOtherPlayerId:playerID];

    }
// ...
}

好的,现在我们回到 cocos2d-x class (MultiPlayer.mm)。在此 class 中,我在设置多人游戏之前调用了 this->scheduleUpdate();。每帧都会调用更新方法并检查游戏状态是否处于活动状态:

// game loop
void MultiPlayer::update(float fDelta) {

    if ([[MultiPlayerLayer sharedManager] gameStateIsActive] && (!namesSet)) {

        // set names
        player1ID->setString([[[MultiPlayerLayer sharedManager] getPlayer1ID] UTF8String]);
        player1ID->setPosition(Vec2((player1ID->getContentSize().width / 2) + origin.x, heartPosY - 50));
        player1ID->setColor(Color3B::BLACK);

        player2ID->setString([[[MultiPlayerLayer sharedManager] getPlayer2ID] UTF8String]);
        player2ID->setPosition(Vec2((visibleSize.width + origin.x) - (player2ID->getContentSize().width / 2), heartPosY - 50));
        player2ID->setColor(Color3B::BLACK);

        namesSet = true;

    }
// ...
}

我的其余代码似乎工作正常。当我在两台设备上测试我的游戏时,第一个配对游戏运行良好。玩家 1 和玩家 2 被选中,字符串 (playerID) 作为我创建的标签显示在屏幕上。我可以分辨出玩家 1 和玩家 2 之间的区别,并且消息发送工作正常。游戏结束时,我会显示获胜者的玩家 ID,然后等待输入。收到输入后(点击屏幕),玩家设备上的比赛将断开。因此,例如,如果玩家 1 首先点击屏幕,则玩家 1 会断开连接并返回主菜单。玩家 2 仍将在游戏中,直到他们选择断开与比赛的连接。这是我在控制台中收到的输出(对于我玩的第一款游戏):

Setting up game...

Waiting for match

Received random number: 2119557985d, ours 1863796654d

We are player 2

Match started

Waiting for start

Active

plugin com.apple.GameCenterUI.GameCenterMatchmakerExtension invalidated

Suspended

Waiting for game over

Match ended

Done

这是MultiPlayer.mm中等待结束比赛的方法:

void MultiPlayer::waitForGameOver(float dt) {

    // wait for user interaction
    if (userDefault->getBoolForKey("gameOverResults-MultiPlayer")) {

        // unschedule
        this->unschedule(schedule_selector(MultiPlayer::waitForGameOver));

        // end game, return to play menu
        [[MultiPlayerLayer sharedManager] matchEnded];

    }

}

并且在 MultiPlayerLayer.mm...

- (void)matchEnded {
    [self setGameState:kGameStateDone];
    [[GCHelper sharedInstance].match disconnect];
    [GCHelper sharedInstance].match = nil;

    // this function replaces the scene in cocos2d-x
   // and returns the player to the main menu
    goBack();
}

更多信息:我正在使用 extern "C" 作为我在 MultiPlayerLayer.mm 内部调用的 C++ 函数。现在当比赛断开时,玩家 returns 到主菜单,一切都很好。我再次测试我的游戏,并在 "SECOND" 时间再次开始配对。这是一切都出错的时候。这是我在控制台中收到的输出(针对我玩的 "SECOND" 游戏):

Setting up game...

Waiting for match

Received random number: 1726927477d, ours 1604807545d

We are player 2

Active

Match started

Waiting for start

plugin com.apple.GameCenterUI.GameCenterMatchmakerExtension invalidated

Game is not active yet. // <- I am unable to play because the game is not active

这是我玩的另一个 "SECOND" 游戏,遇到了不同的问题。它将游戏状态设置为活动两次。此外,在我的设备上我正在玩玩家 1,在我的第二个测试设备上我正在玩玩家 1:

Setting up game...

Waiting for match

Received random number: 588896416d, ours 3091892431d

We are player 1

Active

Match started

Waiting for start

Active

plugin com.apple.GameCenterUI.GameCenterMatchmakerExtension invalidated

我很困惑为什么会这样。它总是在第一个游戏中起作用,然后就不起作用了。有谁知道这是否是游戏中心端的问题,或者比赛结束后设备中是否存储了需要在第二场比赛开始前释放的东西,或者这仅仅是之前设置玩家号码的问题游戏状态是active?谢谢。

终于解决了:

- (void)matchEnded {
    [self setGameState:kGameStateDone];
    [[GCHelper sharedInstance].match disconnect];
    [GCHelper sharedInstance].match = nil;

    // release
    [otherPlayerID release];
    otherPlayerID = nil;

    isPlayer1 = NO;
    receivedRandom = NO;

    // this function replaces the scene in cocos2d-x
   // and returns the player to the main menu
    goBack();
}