swiftgamekit

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


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:

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?


Solution

  • 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")
            }
        }
    }