使用 GameKit 集成 Game Center 功能。适用于处理 GKLocalPlayer 身份验证、检查玩家功能限制、提交排行榜分数、上报成就进度、实现实时或回合制匹配、处理 GKMatch 数据、展示 Game Center 仪表盘或入口点(Access Point)、添加挑战与好友邀请、保存游戏数据或在服务端验证玩家身份等场景。
GameKit
使用 GameKit 实现 Game Center 身份验证、竞技比赛、玩家匹配、社交界面以及存档同步;渲染、棋盘逻辑和完整的 SharePlay Group Activities 架构请交由对应的框架 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 进行防护检查。如需在服务端验证玩家身份,请参阅 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 属性。有关周期性排行榜时间安排、排行榜图标和排行榜组(Leaderboard 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() 重置所有成就进度。
实时多人对战
实时多人对战通过点对点(P2P)网络连接玩家以进行同步游戏。玩家直接通过 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 前未进行身份验证
// 错误示范
func submitScore() {
GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,
leaderboardIDs: ["scores"]) { _ in }




