Gamekit

作者 dpearson26998d90fd121a26無授權條款1.1K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫2 個月前更新

Integrate Game Center features using GameKit. Use when authenticating GKLocalPlayer, checking player restrictions, submitting leaderboard scores, reporting achievements, implementing real-time or turn-based matchmaking, handling GKMatch data, showing the Game Center dashboard or access point, adding challenges and friend invitations, saving game data, or verifying player identity on a server.

AI 產生的概覽

指導整合 Apple GameKit 的 Game Center 功能,例如身分驗證、排行榜、成就與配對。

功能
此技能提供透過 Apple GameKit 框架為 App 加入 Game Center 功能的說明與 Swift 程式碼範例。內容涵蓋玩家身分驗證、限制檢查、存取點與儀表板呈現、排行榜分數提交、成就回報、即時與回合制配對、對局資料交換,以及常見錯誤與審查清單。它產出的是指引與程式碼片段,而非檔案或指令碼。
適用情境
適用於在 Apple 平台 App 中實作或審查 Game Center 整合時,例如驗證 GKLocalPlayer、提交分數、回報成就或建立多人配對。也適合檢查玩家限制以及在伺服器上驗證玩家身分。
執行需求
需要一個使用 GameKit 框架的 Apple 平台專案,在 Xcode 簽署設定中啟用 Game Center 功能,並在 App Store Connect 中設定排行榜與成就。它不附帶指令碼,僅引用一份模式文件與外部 GameKit 文件。

GameKit

Use GameKit for Game Center authentication, competition, matchmaking, social surfaces, and saved-game handoffs; keep rendering, board logic, and full SharePlay group-activity design in their owning framework skills.

Contents

Authentication

All GameKit features require the local player to authenticate first. Set the authenticateHandler on GKLocalPlayer.local early in the app lifecycle. GameKit calls the handler multiple times during initialization.

swift
import GameKit
func authenticatePlayer() {    GKLocalPlayer.local.authenticateHandler = { viewController, error in        if let viewController {            // Present so the player can sign in or create an account.            present(viewController, animated: true)            return        }        if let error {            // Player could not sign in. Disable Game Center features.            disableGameCenter()            return        }
        // Player authenticated. Check restrictions before starting.        let player = GKLocalPlayer.local
        if player.isUnderage {            hideExplicitContent()        }        if player.isMultiplayerGamingRestricted {            disableMultiplayer()        }        if player.isPersonalizedCommunicationRestricted {            disableInGameChat()        }
        configureAccessPoint()    }}

Guard on GKLocalPlayer.local.isAuthenticated before calling any GameKit API. For server-side identity verification, see references/gamekit-patterns.md [blocked].

Access Point

GKAccessPoint displays a Game Center control in a corner of the screen. When tapped, it opens the Game Center dashboard. Configure it after authentication.

swift
func configureAccessPoint() {    GKAccessPoint.shared.location = .topLeading    GKAccessPoint.shared.showHighlights = true    GKAccessPoint.shared.isActive = true}

Hide the access point during gameplay and show it on menu screens:

swift
GKAccessPoint.shared.isActive = false  // Hide during active gameplayGKAccessPoint.shared.isActive = true   // Show on pause or menu

Open the dashboard to a specific state programmatically. Specific leaderboard access-point triggers require iOS 18+.

swift
// Open directly to a leaderboardGKAccessPoint.shared.trigger(    leaderboardID: "com.mygame.highscores",    playerScope: .global,    timeScope: .allTime) { }
// Open directly to achievementsGKAccessPoint.shared.trigger(state: .achievements) { }

Dashboard

Present the Game Center dashboard using GKGameCenterViewController. The presenting object must conform to GKGameCenterControllerDelegate.

swift
final class GameViewController: UIViewController, GKGameCenterControllerDelegate {
    func showDashboard() {        let vc = GKGameCenterViewController(state: .dashboard)        vc.gameCenterDelegate = self        present(vc, animated: true)    }
    func showLeaderboard(_ leaderboardID: String) {        let vc = GKGameCenterViewController(            leaderboardID: leaderboardID,            playerScope: .global,            timeScope: .allTime        )        vc.gameCenterDelegate = self        present(vc, animated: true)    }
    func gameCenterViewControllerDidFinish(        _ gameCenterViewController: GKGameCenterViewController    ) {        gameCenterViewController.dismiss(animated: true)    }}

Dashboard states include .dashboard, .leaderboards, .achievements, .challenges, .localPlayerProfile, and .localPlayerFriendsList.

Leaderboards

Configure leaderboards in App Store Connect before submitting scores. Supports classic (persistent) and recurring (time-limited, auto-resetting) types.

Submitting Scores

Submit to one or more leaderboards using the class method:

swift
func submitScore(_ score: Int, leaderboardIDs: [String]) async throws {    try await GKLeaderboard.submitScore(        score,        context: 0,        player: GKLocalPlayer.local,        leaderboardIDs: leaderboardIDs    )}

Loading Entries

swift
func loadTopScores(    leaderboardID: String,    count: Int = 10) async throws -> (GKLeaderboard.Entry?, [GKLeaderboard.Entry]) {    let leaderboards = try await GKLeaderboard.loadLeaderboards(        IDs: [leaderboardID]    )    guard let leaderboard = leaderboards.first else { return (nil, []) }
    let (localEntry, entries, _) = try await leaderboard.loadEntries(        for: .global,        timeScope: .allTime,        range: 1...count    )    return (localEntry, entries)}

GKLeaderboard.Entry provides player, rank, score, formattedScore, context, and date. For recurring leaderboard timing, leaderboard images, and leaderboard sets, see references/gamekit-patterns.md [blocked].

Achievements

Configure achievements in App Store Connect. Each achievement has a unique identifier, point value, and localized title/description.

Reporting Progress

Set percentComplete from 0...100. The property type is Double, but Apple requires an integer value. GameKit only accepts increases.

swift
func reportAchievement(identifier: String, percentComplete: Int) async throws {    let achievement = GKAchievement(identifier: identifier)    achievement.percentComplete = Double(min(max(percentComplete, 0), 100))    achievement.showsCompletionBanner = true    try await GKAchievement.report([achievement])}
// Unlock an achievement completelyfunc unlockAchievement(_ identifier: String) async throws {    try await reportAchievement(identifier: identifier, percentComplete: 100)}

Loading Player Achievements

swift
func loadPlayerAchievements() async throws -> [GKAchievement] {    try await GKAchievement.loadAchievements()}

If an achievement is not returned, the player has no progress on it yet. Create a new GKAchievement(identifier:) to begin reporting. Use GKAchievement.resetAchievements() to reset all progress during testing.

Real-Time Multiplayer

Real-time multiplayer connects players in a peer-to-peer network for simultaneous gameplay. Players exchange data directly through GKMatch.

Matchmaking with GameKit UI

Use GKMatchmakerViewController for the standard matchmaking interface:

swift
func presentMatchmaker() {    let request = GKMatchRequest()    request.minPlayers = 2    request.maxPlayers = 4    request.inviteMessage = "Join my game!"
    guard let matchmakerVC = GKMatchmakerViewController(matchRequest: request) else {        return    }    matchmakerVC.matchmakerDelegate = self    present(matchmakerVC, animated: true)}

Implement GKMatchmakerViewControllerDelegate:

swift
extension GameViewController: GKMatchmakerViewControllerDelegate {    func matchmakerViewController(        _ viewController: GKMatchmakerViewController,        didFind match: GKMatch    ) {        match.delegate = self        viewController.dismiss(animated: true)        startGame(with: match)    }
    func matchmakerViewControllerWasCancelled(        _ viewController: GKMatchmakerViewController    ) {        viewController.dismiss(animated: true)    }
    func matchmakerViewController(        _ viewController: GKMatchmakerViewController,        didFailWithError error: Error    ) {        viewController.dismiss(animated: true)    }}

Exchanging Data

Send and receive game state through GKMatch and GKMatchDelegate:

swift
extension GameViewController: GKMatchDelegate {    func sendAction(_ action: GameAction, to match: GKMatch) throws {        let data = try JSONEncoder().encode(action)        try match.sendData(toAllPlayers: data, with: .reliable)    }
    func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) {        guard let action = try? JSONDecoder().decode(GameAction.self, from: data) else {            return        }        handleRemoteAction(action, from: player)    }
    func match(_ match: GKMatch, player: GKPlayer, didChange state: GKPlayerConnectionState) {        switch state {        case .connected:            checkIfReadyToStart(match)        case .disconnected:            handlePlayerDisconnected(player)        default:            break        }    }}

Data modes: .reliable sends until delivery succeeds or the connection times out; .unreliable sends once and may arrive out of order. Use .reliable for critical state and .unreliable for small, time-sensitive updates. Treat received match data as untrusted input. Register the local player as a listener (GKLocalPlayer.local.register(self)) to receive invitations. For programmatic matchmaking and custom match UI, see references/gamekit-patterns.md [blocked].

Turn-Based Multiplayer

Turn-based games store match state on Game Center servers. Players take turns asynchronously and do not need to be online simultaneously.

Starting a Match

swift
let request = GKMatchRequest()request.minPlayers = 2request.maxPlayers = 4
let matchmakerVC = GKTurnBasedMatchmakerViewController(matchRequest: request)matchmakerVC.turnBasedMatchmakerDelegate = selfpresent(matchmakerVC, animated: true)

Taking Turns

Encode game state into Data, end the turn, and specify the next participants:

swift
func endTurn(match: GKTurnBasedMatch, gameState: GameState) async throws {    let data = try JSONEncoder().encode(gameState)
    // Build next participants list: remaining active players    let nextParticipants = match.participants.filter {        $0.status != .done && $0 != match.currentParticipant    }
    try await match.endTurn(        withNextParticipants: nextParticipants,        turnTimeout: GKTurnTimeoutDefault,        match: data    )}

Ending the Match

Set outcomes for all participants, then end the match:

swift
func endMatch(_ match: GKTurnBasedMatch, winnerIndex: Int, data: Data) async throws {    for (index, participant) in match.participants.enumerated() {        participant.matchOutcome = (index == winnerIndex) ? .won : .lost    }    try await match.endMatchInTurn(withMatch: data)}

Listening for Turn Events

Register as a listener. Prefer GKLocalPlayerListener when one object handles multiple Game Center event categories.

swift
GKLocalPlayer.local.register(self)
extension GameViewController: GKLocalPlayerListener {    func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch,                didBecomeActive: Bool) {        // Load match data and update UI        loadAndDisplayMatch(match)    }
    func player(_ player: GKPlayer, matchEnded match: GKTurnBasedMatch) {        showMatchResults(match)    }}

Match Data Size

Check the match object's matchDataMaximumSize before ending a turn. Store larger state externally and keep only compact references in match data.

Common Mistakes

Not authenticating before using GameKit APIs

swift
// DON'Tfunc submitScore() {    GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,                              leaderboardIDs: ["scores"]) { _ in }}
// DOfunc submitScore() async throws {    guard GKLocalPlayer.local.isAuthenticated else { return }    try await GKLeaderboard.submitScore(        100, context: 0, player: GKLocalPlayer.local, leaderboardIDs: ["scores"]    )}

Setting authenticateHandler multiple times

swift
// DON'T: Set handler on every scene transitionoverride func viewDidAppear(_ animated: Bool) {    super.viewDidAppear(animated)    GKLocalPlayer.local.authenticateHandler = { vc, error in /* ... */ }}
// DO: Set the handler once, early in the app lifecycle

Ignoring multiplayer restrictions

swift
// DON'Tfunc showMultiplayerMenu() { presentMatchmaker() }
// DOfunc showMultiplayerMenu() {    guard !GKLocalPlayer.local.isMultiplayerGamingRestricted else { return }    presentMatchmaker()}

Not setting match delegate immediately

swift
// DON'T: Set delegate in dismiss completion -- misses early messagesfunc matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {    vc.dismiss(animated: true) { match.delegate = self }}
// DO: Set delegate before dismissingfunc matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {    match.delegate = self    vc.dismiss(animated: true)}

Not calling finishMatchmaking for programmatic matches

swift
// DON'Tlet match = try await GKMatchmaker.shared().findMatch(for: request)startGame(with: match)
// DOlet match = try await GKMatchmaker.shared().findMatch(for: request)GKMatchmaker.shared().finishMatchmaking(for: match)startGame(with: match)

Not disconnecting from match

swift
// DON'Tfunc returnToMenu() { showMainMenu() }
// DOfunc returnToMenu() {    currentMatch?.disconnect()    currentMatch?.delegate = nil    currentMatch = nil    showMainMenu()}

Review Checklist

  • GKLocalPlayer.local.authenticateHandler set once at app launch
  • isAuthenticated checked before any GameKit API call
  • Player restrictions checked (isUnderage, isMultiplayerGamingRestricted, isPersonalizedCommunicationRestricted)
  • Game Center capability added in Xcode signing settings
  • Leaderboards and achievements configured in App Store Connect
  • Access point configured and toggled appropriately during gameplay
  • GKGameCenterControllerDelegate dismisses dashboard in gameCenterViewControllerDidFinish
  • Match delegate set immediately when match is found
  • finishMatchmaking(for:) called for programmatic matches; disconnect() and nil delegate on exit
  • Turn-based match data stays under match.matchDataMaximumSize
  • Turn-based participants have outcomes set before endMatchInTurn
  • Invitation or turn listener registered with GKLocalPlayer.local.register(_:)
  • Data mode chosen appropriately: .reliable for state, .unreliable for frequent updates
  • Error handling for all async GameKit calls

References

來源與署名

來源:dpearson2699/swift-ios-skills位於skills/gamekit提交8d90fd1

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架

更多來自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指導實作、審查與改進 iOS、iPadOS、watchOS 與 CarPlay 上的 WidgetKit 小工具與控制項。

Software Development1.1K2 個月前更新

Weatherkit

dpearson2699

指導 iOS 開發者使用 WeatherService 取得 WeatherKit 預報、警報與署名資訊。

Software Development1.1K2 個月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分類1.1K2 個月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分類1.1K2 個月前更新

Tabletopkit

dpearson2699

指導使用 TabletopKit 在 visionOS 上打造多人空間桌遊,涵蓋棋具、座位、動作與 RealityKit 算繪。

Software Development1.1K2 個月前更新

Swiftui Webkit

dpearson2699

指導在 iOS 26 及更新版本的 SwiftUI App 中使用 WebKit for SwiftUI 嵌入與控制網頁內容。

Software Development1.1K2 個月前更新