使用 GameKit 整合 Game Center 功能。適用於驗證 GKLocalPlayer、檢查玩家限制、提交排行榜分數、回報成就、實作即時或回合制對戰配對、處理 GKMatch 資料、顯示 Game Center 儀表板或存取點(Access Point)、新增挑戰與好友邀請、儲存遊戲資料,或是於伺服器端驗證玩家身分。
GameKit
使用 GameKit 處理 Game Center 身份驗證、競賽、配對、社交介面與遊戲存檔轉移;請將渲染(rendering)、棋牌與盤面邏輯(board logic)以及完整的 SharePlay 群組活動設計留給各自對應的框架 skill 處理。
目錄
身份驗證
所有 GameKit 功能都需要本機玩家先完成身份驗證。請在應用程式生命週期的早期,設定 GKLocalPlayer.local 的 authenticateHandler。GameKit 會在初始化期間多次呼叫此處理程序。
import GameKit
func authenticatePlayer() {
GKLocalPlayer.local.authenticateHandler = { viewController, error in
if let viewController {
// 呈現畫面,讓玩家可以登入或建立帳號。
present(viewController, animated: true)
return
}
if let error {
// 玩家無法登入。停用 Game Center 功能。
disableGameCenter()
return
}
// 玩家已通過身份驗證。在開始前檢查限制設定。
let player = GKLocalPlayer.local
if player.isUnderage {
hideExplicitContent()
}
if player.isMultiplayerGamingRestricted {
disableMultiplayer()
}
if player.isPersonalizedCommunicationRestricted {
disableInGameChat()
}
configureAccessPoint()
}
}
呼叫任何 GameKit API 之前,請務必先檢查並確認 GKLocalPlayer.local.isAuthenticated 為 true。關於伺服器端的身份驗證,請參閱 references/gamekit-patterns.md。
存取點 (Access Point)
GKAccessPoint 會在螢幕角落顯示 Game Center 控制項。玩家點擊時會開啟 Game Center 儀表板。請在完成身份驗證後進行設定。
func configureAccessPoint() {
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.showHighlights = true
GKAccessPoint.shared.isActive = true
}
在遊戲進行時隱藏存取點,並在選單畫面中顯示:
GKAccessPoint.shared.isActive = false // 遊戲進行期間隱藏
GKAccessPoint.shared.isActive = true // 暫停或選單畫面時顯示
可以透過程式碼直接將儀表板開啟至指定狀態。特定排行榜的存取點觸發功能需要 iOS 18+。
// 直接開啟至指定排行榜
GKAccessPoint.shared.trigger(
leaderboardID: "com.mygame.highscores",
playerScope: .global,
timeScope: .allTime
) { }
// 直接開啟至成就頁面
GKAccessPoint.shared.trigger(state: .achievements) { }
儀表板 (Dashboard)
使用 GKGameCenterViewController 呈現 Game Center 儀表板。負責呈現的物件必須符合 GKGameCenterControllerDelegate 協定。
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、.leaderboards、.achievements、.challenges、.localPlayerProfile 與 .localPlayerFriendsList。
排行榜
提交分數前,請先在 App Store Connect 中設定排行榜。支援經典型(永久)與循環型(限時、自動重置)兩種類型。
提交分數
使用類別方法將分數提交至一個或多個排行榜:
func submitScore(_ score: Int, leaderboardIDs: [String]) async throws {
try await GKLeaderboard.submitScore(
score,
context: 0,
player: GKLocalPlayer.local,
leaderboardIDs: leaderboardIDs
)
}
載入排行榜資料
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 提供 player、rank、score、formattedScore、context 與 date 等屬性。關於循環排行榜時間、排行榜圖片及排行榜集錦(sets),請參閱 references/gamekit-patterns.md。
成就
請在 App Store Connect 中設定成就。每個成都有唯一的識別碼(identifier)、分數數值以及在地化的標題與描述。
回報成就進度
將 percentComplete 設定為 0...100。雖然此屬性型別為 Double,但 Apple 要求傳入整數值。此外,GameKit 只接受遞增的進度。
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])
}
// 完整解鎖成就
func unlockAchievement(_ identifier: String) async throws {
try await reportAchievement(identifier: identifier, percentComplete: 100)
}
載入玩家成就
func loadPlayerAchievements() async throws -> [GKAchievement] {
try await GKAchievement.loadAchievements()
}
如果未回傳某項成就,表示玩家該成就尚無任何進度。可建立新的 GKAchievement(identifier:) 開始回報進度。測試期間,可使用 GKAchievement.resetAchievements() 重置所有進度。
即時多人對戰
即時多人對戰透過點對點(peer-to-peer)網路連線玩家以進行同步遊戲。玩家之間直接透過 GKMatch 交換資料。
使用 GameKit UI 進行配對
使用 GKMatchmakerViewController 來呈現標準配對介面:
func presentMatchmaker() {
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
request.inviteMessage = "加入我的遊戲!"
guard let matchmakerVC = GKMatchmakerViewController(matchRequest: request) else {
return
}
matchmakerVC.matchmakerDelegate = self
present(matchmakerVC, animated: true)
}
實作 GKMatchmakerViewControllerDelegate:
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)
}
}
交換資料
透過 GKMatch 與 GKMatchDelegate 發送及接收遊戲狀態:
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
}
}
}
傳輸模式:.reliable 會持續發送直到成功送達或連線逾時;.unreliable 只發送一次且可能未按順序到達。關鍵狀態請使用 .reliable,輕量且具時效性的更新則使用 .unreliable。請將接收到的對戰資料視為不可信輸入。註冊本機玩家為監聽器(GKLocalPlayer.local.register(self))以接收邀請。關於以程式碼進行配對與自訂配對 UI,請參閱 references/gamekit-patterns.md。
回合制多人對戰
回合制遊戲將對戰狀態儲存於 Game Center 伺服器。玩家非同步輪流進行操作,無需同時在線。
開始對戰
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
let matchmakerVC = GKTurnBasedMatchmakerViewController(matchRequest: request)
matchmakerVC.turnBasedMatchmakerDelegate = self
present(matchmakerVC, animated: true)
進行回合
將遊戲狀態編碼為 Data,結束該回合,並指定下一位參與者:
func endTurn(match: GKTurnBasedMatch, gameState: GameState) async throws {
let data = try JSONEncoder().encode(gameState)
// 建立下一位參與者清單:剩餘的活躍玩家
let nextParticipants = match.participants.filter {
$0.status != .done && $0 != match.currentParticipant
}
try await match.endTurn(
withNextParticipants: nextParticipants,
turnTimeout: GKTurnTimeoutDefault,
match: data
)
}
結束對戰
設定所有參與者的對戰結果,然後結束對戰:
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)
}
監聽回合事件
註冊為監聽器。當單一物件需要處理多個 Game Center 事件類別時,優先使用 GKLocalPlayerListener。
GKLocalPlayer.local.register(self)
extension GameViewController: GKLocalPlayerListener {
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch,
didBecomeActive: Bool) {
// 載入對戰資料並更新 UI
loadAndDisplayMatch(match)
}
func player(_ player: GKPlayer, matchEnded match: GKTurnBasedMatch) {
showMatchResults(match)
}
}
對戰資料大小限制
結束回合前,請檢查對戰物件的 matchDataMaximumSize。較大的狀態資料請儲存於外部,對戰資料中僅保留精簡的參照。
常見錯誤
使用 GameKit API 前未先進行身份驗證
// 請勿這樣做 (DON'T)
func submitScore() {
GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,
leaderboardIDs: ["scores"]) { _ in }
<!-- truncated for translation batch; full body continues in source -->




