Search code examples
iosobjective-ccocos2d-xgame-center

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


This is my first time implementing matchmaking into my game. I am using the cocos2d-x v3.x framework. The game is written in C++ and I am also taking advantage of Apple's GameKit framework (Objective-C).

I have been following Ray Wenderlich's tutorial from the start: http://tinyurl.com/j8uoftg

I am wondering if anyone out there has had the same problem that I am encountering. I'll show the main issue below. Any help or advice would be greatly appreciated...

First, I set up matchmaking on init in cocos2d-x (MultiPlayer.mm):

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

    //...

    [[MultiPlayerLayer sharedManager] setUpGame];
}

Inside 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];
}

Everything goes fine on the Game Center side. I am prompted with play now and Game Center looks up the players. After the players are found, the delegate is notified that the match can begin (GCHelper.m):

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

matchStarted = YES;
[delegate matchStarted];
}

Now we are back in MultiPlayerLayer.mm...

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

When the sendRandomNumber message is sent, we determine who is player 1 and player 2 by generating a random number (which is working btw):

- (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];
        }

    }
// ...
}

Then we try to start the game. So if I'm player 1 and waiting we will set the game state to active, set up the strings (get the other player id) and send a message to the other side to begin the game:

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

}

When the sendGameBegin message is sent we set the state to active on the other side and setup the player id strings:

- (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];

    }
// ...
}

Ok now we are back in the cocos2d-x class (MultiPlayer.mm). Inside this class I called this->scheduleUpdate(); before i setup multiplayer. The update method gets called every frame and checks if the game state is active:

// 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;

    }
// ...
}

The rest of my code seems to be working correctly. When I test my game on two devices the first matchmaking game works fine. Player 1 and Player 2 are selected and the strings (playerID's) are displayed on the screen as labels that I create. I can tell the difference between player 1 and player 2 and the message sending is working perfectly. When the game ends I display the playerID of the winner and then wait for input. When input is received (a tap on the screen), the match is disconnected on the player's device. So for example, If player 1 taps the screen first, player 1 disconnects and is brought back to the main menu. Player 2 will still be in the game until they choose to disconnect from the match. Here is the output that I receive in the console (for the first game that I play):

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

This is the method inside MultiPlayer.mm that waits to end the match:

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];

    }

}

And in 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();
}

Some more info: I am using extern "C" for the C++ functions that I am calling inside of MultiPlayerLayer.mm. Now when the match disconnects, the player returns to the main menu and everything is fine. I test my game again and start matchmaking again for the "SECOND" time. This is when everything goes wrong. Here is the output that I receive in the console (for the "SECOND" game that I play):

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

And here's another "SECOND" game that I play with a different problem. It sets the game state to active twice. Also, on my device I am playing as player 1 and on my second test device I am playing as player 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

I am very confused as to why this is happening. It ALWAYS works the first game then it doesn't work after that. Does anyone know if this is a problem on game center's end, or is there something being stored in the device after the match ends that needs to be released before the second game starts, or is it simply just an issue with setting the player numbers before the game state is active? Thanks.


Solution

  • Finally Solved:

    - (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();
    }