iosobjective-cgame-centergamekitgkmatchmaker

iOS Game Center GameKit Programmatic Invite Matchmaking


I'm trying to implement a real-time multiplayer game with a custom UI (no GKMatchMakerViewController). I'm using startBrowsingForNearbyPlayersWithReachableHandler: ^(NSString *playerID, BOOL reachable) to find a local player, and then initiating a match request with the GKMatchmaker singleton (which I have already initiated).

Here's where I'm having trouble. When I send a request, the completion handler fires almost immediately, without an error, and the match it returns has an expected player count of zero. Meanwhile, the other player definitely has not responded to the request

Relevant Code:

- (void) findMatch {
  GKMatchRequest *request = [[GKMatchRequest alloc] init];
  request.minPlayers = NUM_PLAYERS_PER_MATCH; //2
  request.maxPlayers = NUM_PLAYERS_PER_MATCH; //2
  if (nil != self.playersToInvite) {
    // we always successfully get in this if-statement
    request.playersToInvite = self.playersToInvite;
    request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse response) {
      [self.delegate updateUIForPlayer: playerID accepted: (response == GKInviteeResponseAccepted)];
  };
}
request.inviteMessage = @"Let's Play!";

[self.matchmaker findMatchForRequest:request withCompletionHandler:^(GKMatch *match, NSError *error) {
  if (error) {
    // Print the error
    NSLog(@"%@", error.localizedDescription);
  } else 
    if (match != nil) {
      self.currentMatch = match;
      self.currentMatch.delegate = self;

      // All players are connected
      if (match.expectedPlayerCount == 0) {
        // start match
        [self startMatch];
      }
        [self stopLookingForPlayers];
      }
  }];
}

I know from a previous question (iOS Gamecenter Programmatic Matchmaking) that I need to include this:

- (void)matchForInvite:(GKInvite *)invite completionHandler:(void (^)(GKMatch *match, NSError *error))completionHandler

in the above code but I don't know where that should be included. I have tried it both the GKMatchRequest inviteeResponseHandler, and in the matchmaker finMatchForRequest:withCompletionHandler to no avail. The behavior that happens is that the matchmaker returns a match instantly (even before the invitee has been invited) and the matchRequest inviteeResponseHandler is never called even after the invitee taps the match invite.

Can someone offer advice on this? Thank you.

...Jim


Solution

  • I just got this working on my game tonight. There is more negotiation you need to do to get the communication channel setup. The initial match returned to the inviter is waiting for the invitee to respond... Here is my process with only two players. Here are all the steps my communication spin-up is performing. Obviously, no real error handling included here:

    First, Authenticate your player

    Second, right after authentication set inviteHandler. Something like this:

    [GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite* acceptedInvite, NSArray *playersToInvite)
    {
        if(acceptedInvite != nil)
        {
            // Get a match for the invite we obtained...
            [[GKMatchmaker sharedMatchmaker] matchForInvite:acceptedInvite completionHandler:^(GKMatch *match, NSError *error)
            {
                if(match != nil)
                {
                    [self disconnectMatch];
                    // Record the new match...
                    self.MM_gameCenterCurrentMatch = match;
                    self.MM_gameCenterCurrentMatch.delegate = self;
                 }
                else if(error != nil)
                {
                    NSLog(@"ERROR: From matchForInvite: %@", [error description]);
                }
                else 
                {
                    NSLog(@"ERROR: Unexpected return from matchForInvite...");
                }
             }];
        }
    };
    

    Third, Get your list of friend playerIds (not alias).

    Fourth, Setup your GKMatchRequest something like this... I am only inviting one friend:

    // Initialize the match request - Just targeting iOS 6 for now...
    GKMatchRequest* request = [[GKMatchRequest alloc] init];
    request.minPlayers = 2;
    request.maxPlayers = 2;
    request.playersToInvite = [NSArray arrayWithObject:player.playerID];
    request.inviteMessage = @"Let's play!";
    // This gets called when somebody accepts
    request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse response)
    {
        if (response == GKInviteeResponseAccepted)
        {
            //NSLog(@"DEBUG: Player Accepted: %@", playerID);
            // Tell the infrastructure we are don matching and will start using the match
            [[GKMatchmaker sharedMatchmaker] finishMatchmakingForMatch:self.MM_gameCenterCurrentMatch];
         }
    };
    

    Fifth, Use the request to call findMatchForRequest:withCompletionHandler: something like this...

        [[GKMatchmaker sharedMatchmaker] findMatchForRequest:request withCompletionHandler:^(GKMatch* match, NSError *error) {
        if (error)
        {
            NSLog(@"ERROR: Error makeMatch: %@", [error description] );
            [self disconnectMatch];
        }
        else if (match != nil)
        {
            // Record the new match and set me up as the delegate...
            self.MM_gameCenterCurrentMatch = match;
            self.MM_gameCenterCurrentMatch.delegate = self;
            // There will be no players until the players accept...
        }
    }];
    

    Sixth, this sends the request to the other player and if they accept the "inviteHandler" from the second step gets called.

    Seventh, the "inviteHandler" from the second step gets the match for the GKInvite!

    Eighth, the "inviteeResponseHandler" from the fourth step gets called which finished the match!

    Ninth, create a didChangeState from GKMatchDelegate to handle the finalization of the match. Something like this:

    - (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state{
    switch (state)
    {
        case GKPlayerStateConnected:
            // Handle a new player connection.
            break;
        case GKPlayerStateDisconnected:
            // A player just disconnected.
            break;
    }
    if (!self.matchStarted && match.expectedPlayerCount == 0)
    {
        self.matchStarted = YES;
        // Handle initial match negotiation.
        if (self.iAmHost && !self.sentInitialResponse)
        {
            self.sentInitialResponse = true;
            // Send a hello log entry
            [self sendMessage: [NSString stringWithFormat:@"Message from friend, 'Hello, thanks for accepting, you have connected with %@'", self.MM_gameCenterLocalPlayer.alias] toPlayersInMatch: [NSArray arrayWithObject:playerID]];
        }
    }}
    

    Tenth, here is my sendMessage:

    - (void) sendMessage:(NSString*)action toPlayersInMatch:(NSArray*) playerIds{   
    NSError* err = nil;
    if (![self.MM_gameCenterCurrentMatch sendData:[action dataUsingEncoding:NSUTF8StringEncoding] toPlayers:playerIds withDataMode:GKMatchSendDataReliable error:&err])
    {
        if (err != nil)
        {
            NSLog(@"ERROR: Could not send action to players (%@): %@ (%d) - '%@'" ,[playersInMatch componentsJoinedByString:@","],[err localizedDescription],[err code], action);
        }
        else
        {
            NSLog(@"ERROR: Could not send action to players (%@): null error - '%@'",[playersInMatch componentsJoinedByString:@","], action);
        }
    }
    else
    {
        NSLog(@"DEBUG: Message sent to players (%@) - '%@'",[playersInMatch componentsJoinedByString:@","], action);
    }}
    

    Eleventh, create a didReceiveData from GKMatchDelegate something like this:

    - (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID{
    NSString* actionString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    // Send the initial response after we got the initial send from the
    // invitee...
    if (!self.iAmHost &&!self.sentInitialResponse)
    {
        self.sentInitialResponse = true;
        // Send a hello log entry
        [self sendMessage: [NSString stringWithFormat:@"Message from friend, 'Hello, thanks for inviting, you have connected with %@'", self.MM_gameCenterLocalPlayer.alias] toPlayersInMatch: [NSArray arrayWithObject:playerID]];
    }
    // Execute the action we were sent...
    NSLog(actionString);}
    

    Twelfth... Well now you have the communication channels up and running... do whatever you want...