Can't connect players in GameKit using GKMatchmaker.shared().findMatch

Viewed 65

I'm trying to connect two players with each other using GameKit in a very simple game. I want to use GKMatchmaker.shared().findMatch as I don't want to show any GameCenter related view controllers. (to keep it simple)

Problem:

Even though GameKit creates a match after finding two players, an error occurs that prevents either player from sending any message to the others.

Current Situation:

The basic code is as follows (based on the docs described here: https://developer.apple.com/documentation/gamekit/finding_multiple_players_for_a_game)

print("Requesting multiplayer match")
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 2
request.recipientResponseHandler = {(player: GKPlayer, respnse: GKInviteRecipientResponse) -> Void in
    print("new player about to join")
    print(player.alias)
    print(respnse)
}

GKMatchmaker.shared().findMatch(for: request, withCompletionHandler: {
    (match: GKMatch?, error: Error?) -> Void in
    if error != nil {
        // Handle the error that occurred finding a match.
        print("error during matchmaking")
        print(error as Any)
    } else if match != nil {
        guard let match = match else { return }

        print("connected to \(match.players.count) players")

        // load the multiplayer data handler
        let handler = MultiMatchHandler()
        match.delegate = handler

        // load the multiplayer service
        let service = MultiMatchService(match: match)
        service.sendMessageToAll(text: "Hello from the other side")

        // finish the match making
        GKMatchmaker.shared().finishMatchmaking(for: match)

        // Start the game with the players in the match.
        self.view?.presentScene(GameScene.newScene(multiplayer: service))
    }

})

The output of that is

Requesting multiplayer match
2022-01-05 01:19:16.554959+0100 Grapefruit[38300:10026027] [Match] cannot set connecting state for players: (
    "<GKPlayer: 0x282add280>(alias:... gamePlayerID:... teamPlayerID:... name:... status:(null) friendBiDirectional:0 friendPlayedWith:1 friendPlayedNearby:0 acceptedGameInviteFromThisFriend:0 initiatedGameInviteToThisFriend:0 automatchedTogether:1)"
), as there is no inviteDelegate set yet. The state might directly change to Ready when we set the inviteDelegate later and call sendQueuedStatesAndPackets.
2022-01-05 01:19:16.557002+0100 Grapefruit[38300:10026027] [Match] syncPlayers failed to loadPlayersForLegacyIdentifiers: (
    "..."
)
connected to 0 players
sending text Hello from the other side failed

Findings:

  • minPlayers is set to 2. As the completion handler is called this means that at least one more player was found. But the number of players returned in match.players.count is 0
  • The matcher shows an error saying that cannot set connecting state for players ... as there is no inviteDelegate set yet. I can't find any info about this invite delegate.

Actual Question:

What is an inviteDelegate? Do I really need to implement such (if yes, then how?)? (I don't think so as the docs state that the match only starts after the invites are accepted).

How can I resolve this issue?

1 Answers

here is a working example for you. open on two machines, make sure both are authenticated, press "findMatch()" on both machines (and wait for confirmation), then ping baby ping

i believe the "no inviteDelegate set yet" error doesn't mean the match making necessary failed, and can safely be ignored, as mentioned here

you'll want to implement more of the GKMatchDelegate protocol, but this is a skeleton for demonstration purposes

import SwiftUI
import GameKit
import SpriteKit

class MyGameScene: SKScene, GKMatchDelegate {
    override func didMove(to view: SKView) {
        self.backgroundColor = .yellow
    }
    
    //GKMatchDelegate protocol
    func match(_ match: GKMatch, didReceive data: Data, forRecipient recipient: GKPlayer, fromRemotePlayer player: GKPlayer) {
        print("\(Self.self) \(#function) -- ping received")
    }
}

struct Matchmaker: View {
    @State var isAuthenticated:Bool = false
    @State var scene = MyGameScene()
    @State var match:GKMatch? = nil
    var body: some View {
        ZStack {
            Color.clear
            SpriteView(scene: scene)
            VStack(alignment: .leading, spacing: 20) {
                Text("1) authenticate() \(Image(systemName: isAuthenticated ? "checkmark.icloud" : "xmark.icloud"))")
                Button { findMatch() } label: {
                    Text("2) findMatch() \(Image(systemName: (match != nil) ? "person.fill.checkmark" : "person.fill.xmark"))")
                }
                Button { ping() } label: {
                    Text("3) ping()")
                }
            }
        }
        .onAppear() {
            authenticate()
        }
    }
    
    func authenticate() {
        GKLocalPlayer.local.authenticateHandler = { viewController, error in
            if let error = error { print(error) }
            isAuthenticated = (error == nil)
        }
    }
    
    func findMatch() {
        guard isAuthenticated else { return }
        let request = GKMatchRequest()
        request.minPlayers = 2
        request.maxPlayers = 2
        request.playerAttributes = 0xFFFFFFFF //mask for "i'll match with anyone"

        GKMatchmaker.shared().findMatch (for: request) { match, error in
            if let error = error { print(error) }
            self.match = match
            self.match?.delegate = scene
        }
    }
    
    func ping() {
        let players = match?.players ?? [];
        let data = Data()
        do {
            try match?.send(data, to: players, dataMode: .reliable)
        } catch  {
            print("Sending failed")
        }
    }
}
Related