gamekit
该技能帮助开发者将 Game Center 功能无缝集成到 iOS 游戏中,涵盖玩家认证、排行榜、成就、多人匹配、挑战和游戏数据保存。它简化了复杂的 GameKit API 调用,使游戏能够快速利用 Apple 生态系统的社交和竞技特性,提升玩家参与度和留存率,专注于核心游戏逻辑开发。
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill gamekitBefore / After 效果对比
1 组传统上,将 Game Center 的认证、排行榜、成就和多人匹配等复杂功能集成到 iOS 游戏中,需要开发者花费大量时间阅读文档、编写样板代码并调试各种 API 调用和状态处理,容易出错且耗时。
借助此技能提供的清晰示例和最佳实践,开发者可以快速理解并实现 Game Center 功能,大幅减少集成时间和调试工作,确保功能稳定上线,从而将更多精力投入到游戏核心玩法创新上。
GameKit
Integrate Game Center services into iOS 26+ games using GameKit and Swift 6.3: authentication, leaderboards, achievements, multiplayer matchmaking, access point, dashboard, challenges, and saved games. Keep SpriteKit rendering, SceneKit 3D, TabletopKit board logic, and full SharePlay group-activity design in their framework domains; use GameKit only for Game Center handoff points.
Contents
- Authentication
- Access Point
- Dashboard
- Leaderboards
- Achievements
- Real-Time Multiplayer
- Turn-Based Multiplayer
- Common Mistakes
- Review Checklist
- References
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.
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.
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.
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:
GKAccessPoint.shared.isActive = false // Hide during active gameplay
GKAccessPoint.shared.isActive = true // Show on pause or menu
Open the dashboard to a specific state programmatically. Specific leaderboard access-point triggers require iOS 18+.
// 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) { }
Dashboard
Present the Game Center dashboard using GKGameCenterViewController. The
presenting object must conform to 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 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:
func submitScore(_ score: Int, leaderboardIDs: [String]) async throws {
try await GKLeaderboard.submitScore(
score,
context: 0,
player: GKLocalPlayer.local,
leaderboardIDs: leaderboardIDs
)
}
Loading Entries
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.
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.
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 completely
func unlockAchievement(_ identifier: String) async throws {
try await reportAchievement(identifier: identifier, percentComplete: 100)
}
Loading Player Achievements
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:
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:
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:
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.
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
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 Data, end the turn, and specify the next participants:
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:
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.
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
// 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"]
)
}
Setting authenticateHandler multiple times
// 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 lifecycle
Ignoring multiplayer restrictions
// DON'T
func showMultiplayerMenu() { presentMatchmaker() }
// DO
func showMultiplayerMenu() {
guard !GKLocalPlayer.local.isMultiplayerGamingRestricted else { return }
presentMatchmaker()
}
Not setting match delegate immediately
// 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)
}
Not calling finishMatchmaking for programmatic matches
// 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)
Not disconnecting from match
// DON'T
func returnToMenu() { showMainMenu() }
// DO
func returnToMenu() {
currentMatch?.disconnect()
currentMatch?.delegate = nil
currentMatch = nil
showMainMenu()
}
Review Checklist
-
GKLocalPlayer.local.authenticateHandlerset once at app launch -
isAuthenticatedchecked 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
-
GKGameCenterControllerDelegatedismisses dashboard ingameCenterViewControllerDidFinish - 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:
.reliablefor state,.unreliablefor frequent updates - New voice/social audio uses SharePlay; legacy
GKVoiceChathasNSMicrophoneUsageDescription - Error handling for all async GameKit calls
References
- See references/gamekit-patterns.md for identity verification, legacy voice chat, saved games, custom match UI, leaderboard images, challenge handling, and rule-based m
...
用户评价 (0)
发表评价
暂无评价
统计数据
用户评分
为此 Skill 评分