agentsclimarketplace

Gamekit multiplayer

Skill ibrews/apple-platform-skills/gamekit-multiplayer

GameKit real-time and turn-based multiplayer for iOS and visionOS. Use when implementing GKMatch matchmaking, GKLocalPlayer authentication, real-time data sync, or turn-based game logic using Apple's Game Center infrastructure.From its SKILL.md

Install
npx -y skills add ibrews/apple-platform-skills --skill gamekit-multiplayer

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.5 KB, 983 tokens by cl100k_base, as published. Nobody here has run it

GameKit Multiplayer

Authentication First — Always

Nothing in GameKit works until GKLocalPlayer is authenticated. Call this at app launch, before any matchmaking UI:

import GameKit

func authenticateLocalPlayer() {
    GKLocalPlayer.local.authenticateHandler = { [weak self] viewController, error in
        if let vc = viewController {
            // Present Game Center login UI
            self?.present(vc, animated: true)
        } else if GKLocalPlayer.local.isAuthenticated {
            self?.enableMultiplayerUI()
        } else {
            // Player declined or unavailable — disable multiplayer
            print("GameKit auth failed: \(error?.localizedDescription ?? "unknown")")
        }
    }
}

Common pitfall: calling findMatch before auth completes → silent failure, no error callback.

GKMatchmaker vs GKMatchmakerViewController

GKMatchmakerGKMatchmakerViewController
UIYour own custom UIApple's built-in Game Center UI
Use whenFull control, invite-only flowsQuick integration
// Programmatic (custom UI)
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4

GKMatchmaker.shared().findMatch(for: request) { match, error in
    guard let match, error == nil else { return }
    DispatchQueue.main.async { self.startGame(with: match) }
}

// Built-in Game Center UI
let vc = GKMatchmakerViewController(matchRequest: request)
vc?.matchmakerDelegate = self
present(vc!, animated: true)

Real-time vs Turn-based

GKMatch (real-time):

  • All players online simultaneously
  • Low-latency continuous sync (positions, inputs)
  • Action games, racing, shooters

GKTurnBasedMatch:

  • Players take turns asynchronously (may be hours apart)
  • Apple persists game state on their servers
  • Strategy games, board games, word games

Sending & Receiving Data (Real-time)

// Encode messages with Codable
struct GameMessage: Codable {
    enum Kind: String, Codable { case move, shoot, stateSync }
    let kind: Kind
    let payload: Data
}

func send(_ message: GameMessage, reliable: Bool = true) throws {
    let data = try JSONEncoder().encode(message)
    try match.sendData(toAllPlayers: data, with: reliable ? .reliable : .unreliable)
}
// .reliable   → guaranteed, ordered (use for game state changes)
// .unreliable → fast, may drop (use for position streaming)

// GKMatchDelegate — always dispatch UI work to main thread
func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) {
    guard let message = try? JSONDecoder().decode(GameMessage.self, from: data) else { return }
    DispatchQueue.main.async { self.apply(message, from: player) }
}

Player Connect / Disconnect

func match(_ match: GKMatch, player: GKPlayer, didChange state: GKPlayerConnectionState) {
    switch state {
    case .connected:
        // Wait until ALL expected players are connected
        if match.expectedPlayerCount == 0 { startGame() }
    case .disconnected:
        pauseGame()
        // Optionally: GKMatchmaker.shared().addPlayers(to: match, ...) to fill slot
    @unknown default: break
    }
}

match.expectedPlayerCount starts at (minPlayers - 1) and decrements as players join. Start the game only when it reaches 0.

Sandbox vs Production

  • Test accounts: create sandbox Apple IDs in App Store Connect → Users and Access → Sandbox
  • Sandbox and production Game Center are completely separate — leaderboards, matches, and friends don't cross
  • Enable on device: Settings → Developer → Enable Sandbox Game Center (Xcode scheme also works)
  • Always test with two sandbox accounts to catch auth edge cases

Common Pitfalls

PitfallFix
Matchmaking before authSet authenticateHandler at launch; wait for isAuthenticated callback
UI updates in delegateWrap all UI in DispatchQueue.main.async
Starting too earlyWait for match.expectedPlayerCount == 0
Match object deallocatedKeep strong var match: GKMatch? reference
didFailWithError not implementedNetwork errors only surface here — implement it
Turn-based state lostCall match.saveCurrentTurn(withMatch:completionHandler:) before ending turn

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,790. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.