gamekit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGameKit
GameKit
Integrate Game Center features into iOS 26+ games using GameKit and Swift 6.3.
Provides player authentication, leaderboards, achievements, multiplayer
matchmaking, access point, dashboard, challenges, and saved games.
使用GameKit和Swift 6.3将Game Center功能集成到iOS 26及以上版本的游戏中。提供玩家身份验证、排行榜、成就、多人游戏匹配、接入点、控制台、挑战和游戏存档功能。
Contents
目录
Authentication
身份验证
All GameKit features require the local player to authenticate first. Set the
on early in the app lifecycle.
GameKit calls the handler multiple times during initialization.
authenticateHandlerGKLocalPlayer.localswift
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 before calling any GameKit API.
For server-side identity verification, see references/gamekit-patterns.md.
GKLocalPlayer.local.isAuthenticated所有GameKit功能都需要先对本地玩家进行身份验证。在应用生命周期的早期为设置。GameKit会在初始化过程中多次调用该处理器。
GKLocalPlayer.localauthenticateHandlerswift
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()
}
}在调用任何GameKit API之前,请先检查。如需进行服务器端身份验证,请参考references/gamekit-patterns.md。
GKLocalPlayer.local.isAuthenticatedAccess Point
接入点
GKAccessPointswift
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 gameplay
GKAccessPoint.shared.isActive = true // Show on pause or menuOpen the dashboard to a specific state programmatically:
swift
// Open directly to a leaderboard
GKAccessPoint.shared.trigger(
leaderboardID: "com.mygame.highscores",
playerScope: .global,
timeScope: .allTime
) { }
// Open directly to achievements
GKAccessPoint.shared.trigger(state: .achievements) { }GKAccessPointswift
func configureAccessPoint() {
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.showHighlights = true
GKAccessPoint.shared.isActive = true
}在游戏进行时隐藏接入点,在菜单界面显示:
swift
GKAccessPoint.shared.isActive = false // 游戏进行中隐藏
GKAccessPoint.shared.isActive = true // 暂停或菜单界面显示通过编程方式直接打开控制台到指定状态:
swift
// 直接打开到排行榜
GKAccessPoint.shared.trigger(
leaderboardID: "com.mygame.highscores",
playerScope: .global,
timeScope: .allTime
) { }
// 直接打开到成就页面
GKAccessPoint.shared.trigger(state: .achievements) { }Dashboard
控制台
Present the Game Center dashboard using . The
presenting object must conform to .
GKGameCenterViewControllerGKGameCenterControllerDelegateswift
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: , , , .
.dashboard.leaderboards.achievements.localPlayerProfile使用展示Game Center控制台。展示对象必须遵循协议。
GKGameCenterViewControllerGKGameCenterControllerDelegateswift
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.localPlayerProfileLeaderboards
排行榜
Configure leaderboards in App Store Connect before submitting scores. Supports
classic (persistent) and recurring (time-limited, auto-resetting) types.
在提交分数前,请先在App Store Connect中配置排行榜。支持经典(持久型)和周期性(限时自动重置)两种类型。
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
)
}使用类方法向一个或多个排行榜提交分数:
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.EntryplayerrankscoreformattedScorecontextdateswift
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.EntryplayerrankscoreformattedScorecontextdateAchievements
成就
Configure achievements in App Store Connect. Each achievement has a unique
identifier, point value, and localized title/description.
在App Store Connect中配置成就。每个成就都有唯一的标识符、积分值以及本地化的标题和描述。
Reporting Progress
上报进度
Set from 0.0 to 100.0. GameKit only accepts increases;
setting a lower value than previously reported has no effect.
percentCompleteswift
func reportAchievement(identifier: String, percentComplete: Double) async throws {
let achievement = GKAchievement(identifier: identifier)
achievement.percentComplete = percentComplete
achievement.showsCompletionBanner = true
try await GKAchievement.report([achievement])
}
// Unlock an achievement completely
func unlockAchievement(_ identifier: String) async throws {
try await reportAchievement(identifier: identifier, percentComplete: 100.0)
}设置的值从0.0到100.0。GameKit仅接受进度提升,设置低于之前上报的值将不会产生任何效果。
percentCompleteswift
func reportAchievement(identifier: String, percentComplete: Double) async throws {
let achievement = GKAchievement(identifier: identifier)
achievement.percentComplete = percentComplete
achievement.showsCompletionBanner = true
try await GKAchievement.report([achievement])
}
// 完全解锁成就
func unlockAchievement(_ identifier: String) async throws {
try await reportAchievement(identifier: identifier, percentComplete: 100.0)
}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 to begin reporting. Use
to reset all progress during testing.
GKAchievement(identifier:)GKAchievement.resetAchievements()swift
func loadPlayerAchievements() async throws -> [GKAchievement] {
try await GKAchievement.loadAchievements() ?? []
}如果某个成就未被返回,说明玩家尚未在该成就上取得任何进度。可以创建新的对象开始上报进度。测试时可使用重置所有进度。
GKAchievement(identifier:)GKAchievement.resetAchievements()Real-Time Multiplayer
实时多人游戏
Real-time multiplayer connects players in a peer-to-peer network for
simultaneous gameplay. Players exchange data directly through .
GKMatch实时多人游戏通过对等网络连接玩家,支持同步游戏玩法。玩家通过直接交换数据。
GKMatchMatchmaking with GameKit UI
使用GameKit UI进行匹配
Use for the standard matchmaking interface:
GKMatchmakerViewControllerswift
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 :
GKMatchmakerViewControllerDelegateswift
extension GameViewController: GKMatchmakerViewControllerDelegate {
func matchmakerViewController(
_ viewController: GKMatchmakerViewController,
didFind match: GKMatch
) {
viewController.dismiss(animated: true)
match.delegate = self
startGame(with: match)
}
func matchmakerViewControllerWasCancelled(
_ viewController: GKMatchmakerViewController
) {
viewController.dismiss(animated: true)
}
func matchmakerViewController(
_ viewController: GKMatchmakerViewController,
didFailWithError error: Error
) {
viewController.dismiss(animated: true)
}
}使用实现标准匹配界面:
GKMatchmakerViewControllerswift
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)
}实现协议:
GKMatchmakerViewControllerDelegateswift
extension GameViewController: GKMatchmakerViewControllerDelegate {
func matchmakerViewController(
_ viewController: GKMatchmakerViewController,
didFind match: GKMatch
) {
viewController.dismiss(animated: true)
match.delegate = self
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 and :
GKMatchGKMatchDelegateswift
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: (TCP-like, ordered, guaranteed) and
(UDP-like, faster, no guarantee). Use for critical game state and
for frequent position updates. Register the local player as a
listener () to receive invitations through
. For programmatic matchmaking and custom match UI, see
references/gamekit-patterns.md.
.reliable.unreliable.reliable.unreliableGKLocalPlayer.local.register(self)GKInviteEventListener通过和发送和接收游戏状态:
GKMatchGKMatchDelegateswift
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
}
}
}数据模式包括:(类TCP,有序且可靠)和(类UDP,速度更快但不保证送达)。关键游戏状态使用,频繁的位置更新使用。将本地玩家注册为监听器(),即可通过接收邀请。关于编程式匹配和自定义匹配界面的内容,请参考references/gamekit-patterns.md。
.reliable.unreliable.reliable.unreliableGKLocalPlayer.local.register(self)GKInviteEventListenerTurn-Based Multiplayer
回合制多人游戏
Turn-based games store match state on Game Center servers. Players take turns
asynchronously and do not need to be online simultaneously.
回合制游戏将匹配状态存储在Game Center服务器上。玩家异步轮流进行游戏,无需同时在线。
Starting a Match
开始匹配
swift
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
let matchmakerVC = GKTurnBasedMatchmakerViewController(matchRequest: request)
matchmakerVC.turnBasedMatchmakerDelegate = self
present(matchmakerVC, animated: true)swift
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
let matchmakerVC = GKTurnBasedMatchmakerViewController(matchRequest: request)
matchmakerVC.turnBasedMatchmakerDelegate = self
present(matchmakerVC, animated: true)Taking Turns
进行回合
Encode game state into , end the turn, and specify the next participants:
Dataswift
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.matchOutcome == .none && $0 != match.currentParticipant
}
try await match.endTurn(
withNextParticipants: nextParticipants,
turnTimeout: GKTurnTimeoutDefault,
match: data
)
}将游戏状态编码为,结束当前回合并指定下一位参与者:
Dataswift
func endTurn(match: GKTurnBasedMatch, gameState: GameState) async throws {
let data = try JSONEncoder().encode(gameState)
// 构建下一位参与者列表:剩余活跃玩家
let nextParticipants = match.participants.filter {
$0.matchOutcome == .none && $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)
}为所有参与者设置结果,然后结束匹配:
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 and implement :
GKTurnBasedEventListenerswift
GKLocalPlayer.local.register(self)
extension GameViewController: GKTurnBasedEventListener {
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)
}
}注册为监听器并实现协议:
GKTurnBasedEventListenerswift
GKLocalPlayer.local.register(self)
extension GameViewController: GKTurnBasedEventListener {
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch,
didBecomeActive: Bool) {
// 加载匹配数据并更新UI
loadAndDisplayMatch(match)
}
func player(_ player: GKPlayer, matchEnded match: GKTurnBasedMatch) {
showMatchResults(match)
}
}Match Data Size
匹配数据大小
matchDataMaximumSizematchDataMaximumSizeCommon Mistakes
常见错误
Not authenticating before using GameKit APIs
未验证身份就使用GameKit API
swift
// DON'T
func submitScore() {
GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,
leaderboardIDs: ["scores"]) { _ in }
}
// DO
func submitScore() async throws {
guard GKLocalPlayer.local.isAuthenticated else { return }
try await GKLeaderboard.submitScore(
100, context: 0, player: GKLocalPlayer.local, leaderboardIDs: ["scores"]
)
}swift
// 错误示例
func submitScore() {
GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,
leaderboardIDs: ["scores"]) { _ in }
}
// 正确示例
func 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
多次设置authenticateHandler
swift
// DON'T: Set handler on every scene transition
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
GKLocalPlayer.local.authenticateHandler = { vc, error in /* ... */ }
}
// DO: Set the handler once, early in the app lifecycleswift
// 错误示例:每次场景切换都设置处理器
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
GKLocalPlayer.local.authenticateHandler = { vc, error in /* ... */ }
}
// 正确示例:仅在应用启动时设置一次处理器Ignoring multiplayer restrictions
忽略多人游戏限制
swift
// DON'T
func showMultiplayerMenu() { presentMatchmaker() }
// DO
func showMultiplayerMenu() {
guard !GKLocalPlayer.local.isMultiplayerGamingRestricted else { return }
presentMatchmaker()
}swift
// 错误示例
func showMultiplayerMenu() { presentMatchmaker() }
// 正确示例
func showMultiplayerMenu() {
guard !GKLocalPlayer.local.isMultiplayerGamingRestricted else { return }
presentMatchmaker()
}Not setting match delegate immediately
未立即设置匹配代理
swift
// DON'T: Set delegate in dismiss completion -- misses early messages
func matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {
vc.dismiss(animated: true) { match.delegate = self }
}
// DO: Set delegate before dismissing
func matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {
match.delegate = self
vc.dismiss(animated: true)
}swift
// 错误示例:在关闭完成后设置代理,会错过早期消息
func matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {
vc.dismiss(animated: true) { match.delegate = self }
}
// 正确示例:在关闭前设置代理
func matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {
match.delegate = self
vc.dismiss(animated: true)
}Not calling finishMatchmaking for programmatic matches
未为编程式匹配调用finishMatchmaking
swift
// DON'T
let match = try await GKMatchmaker.shared().findMatch(for: request)
startGame(with: match)
// DO
let match = try await GKMatchmaker.shared().findMatch(for: request)
GKMatchmaker.shared().finishMatchmaking(for: match)
startGame(with: match)swift
// 错误示例
let match = try await GKMatchmaker.shared().findMatch(for: request)
startGame(with: match)
// 正确示例
let match = try await GKMatchmaker.shared().findMatch(for: request)
GKMatchmaker.shared().finishMatchmaking(for: match)
startGame(with: match)Not disconnecting from match
未断开匹配连接
swift
// DON'T
func returnToMenu() { showMainMenu() }
// DO
func returnToMenu() {
currentMatch?.disconnect()
currentMatch?.delegate = nil
currentMatch = nil
showMainMenu()
}swift
// 错误示例
func returnToMenu() { showMainMenu() }
// 正确示例
func returnToMenu() {
currentMatch?.disconnect()
currentMatch?.delegate = nil
currentMatch = nil
showMainMenu()
}Review Checklist
审核检查清单
- set once at app launch
GKLocalPlayer.local.authenticateHandler - checked before any GameKit API call
isAuthenticated - 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
- dismisses dashboard in
GKGameCenterControllerDelegategameCenterViewControllerDidFinish - Match delegate set immediately when match is found
- called for programmatic matches;
finishMatchmaking(for:)and nil delegate on exitdisconnect() - Turn-based match data stays under 64 KB
- Turn-based participants have outcomes set before
endMatchInTurn - Invitation listener registered with
GKLocalPlayer.local.register(_:) - Data mode chosen appropriately: for state,
.reliablefor frequent updates.unreliable - set if using voice chat
NSMicrophoneUsageDescription - Error handling for all async GameKit calls
- 仅在应用启动时设置一次
GKLocalPlayer.local.authenticateHandler - 在调用任何GameKit API前检查状态
isAuthenticated - 检查玩家限制(、
isUnderage、isMultiplayerGamingRestricted)isPersonalizedCommunicationRestricted - 在Xcode签名设置中添加Game Center功能
- 在App Store Connect中配置排行榜和成就
- 正确配置接入点,并在游戏过程中适时切换显示状态
- 在
GKGameCenterControllerDelegate方法中关闭控制台gameCenterViewControllerDidFinish - 找到匹配后立即设置匹配代理
- 为编程式匹配调用;退出时调用
finishMatchmaking(for:)并将代理置为nildisconnect() - 回合制匹配数据大小不超过64 KB
- 回合制匹配在调用前为所有参与者设置结果
endMatchInTurn - 通过注册邀请监听器
GKLocalPlayer.local.register(_:) - 选择合适的数据模式:用于状态同步,
.reliable用于频繁更新.unreliable - 如果使用语音聊天,设置
NSMicrophoneUsageDescription - 为所有异步GameKit调用添加错误处理
References
参考资料
- See references/gamekit-patterns.md for voice chat, saved games, custom match UI, leaderboard images, challenge handling, and rule-based matchmaking.
- GameKit documentation
- GKLocalPlayer
- GKAccessPoint
- GKLeaderboard
- GKAchievement
- GKMatch
- GKTurnBasedMatch
- 关于语音聊天、游戏存档、自定义匹配界面、排行榜图片、挑战处理和规则匹配的内容,请参考references/gamekit-patterns.md。
- GameKit文档
- GKLocalPlayer
- GKAccessPoint
- GKLeaderboard
- GKAchievement
- GKMatch
- GKTurnBasedMatch