gamekit について
GameKit は、Game Center の認証、リーダーボード、実績、マルチプレイヤーを扱う、Apple ゲーム開発者向けの Swift スキルガイドです。API の適切な呼び出し順や、よくある誤り、レビュー項目も説明します。
主な機能
- 01Game Center のプレイヤー認証
- 02リーダーボードと実績の管理
- 03リアルタイム・ターン制マルチプレイヤー
- 04ゲーム状態データの交換
- 05アクセス ポイントとダッシュボードの設定
活用シーン
- 01ゲームへの Game Center サインイン追加
- 02リーダーボードスコアの送信と取得
- 03実績進捗の報告と読み込み
- 04リアルタイムまたは非同期対戦の実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill gamekitインストール後、Claude Code で/gamekitを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/gamekit-patterns.md
詳細
GameKit
Game Center 認証、対戦、マッチメイキング、ソーシャル機能、セーブデータの引き継ぎには GameKit を使用します。レンダリング、ボードロジック、SharePlay のグループアクティビティ設計全般は、それぞれを担当するフレームワークのスキルで扱います。
目次
認証
GameKit のすべての機能を使用するには、まずローカルプレイヤーを認証する必要があります。アプリのライフサイクルの早い段階で、GKLocalPlayer.local に authenticateHandler を設定してください。GameKit は初期化中にハンドラーを複数回呼び出します。
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 を呼び出す前に、GKLocalPlayer.local.isAuthenticated を確認してください。サーバー側での ID 検証については、references/gamekit-patterns.md を参照してください。
アクセスポイント
GKAccessPoint は画面の隅に Game Center のコントロールを表示します。タップすると、Game Center ダッシュボードが開きます。認証後に設定してください。
func configureAccessPoint() {
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.showHighlights = true
GKAccessPoint.shared.isActive = true
}
ゲームプレイ中はアクセスポイントを非表示にし、メニュー画面では表示します。
GKAccessPoint.shared.isActive = false // Hide during active gameplay
GKAccessPoint.shared.isActive = true // Show on pause or menu
ダッシュボードを特定の状態でプログラムから開きます。特定のリーダーボードへのアクセスポイントのトリガーを使用するには、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) { }
ダッシュボード
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 でリーダーボードを設定してください。クラシック(永続型)と定期(期間限定で自動リセット)のタイプに対応しています。
スコアの送信
クラスメソッドを使用して、1 つ以上のリーダーボードに送信します。
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 を提供します。定期リーダーボードの期間、リーダーボードの画像、リーダーボードセットについては、references/gamekit-patterns.md を参照してください。
実績
App Store Connect で実績を設定してください。各実績には、一意の識別子、ポイント値、ローカライズされたタイトルと説明があります。
進行状況の報告
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])
}
// Unlock an achievement completely
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() を使用します。
リアルタイムマルチプレイヤー
リアルタイムマルチプレイヤーでは、ピアツーピアネットワークでプレイヤーを接続し、同時にゲームをプレイします。プレイヤーは GKMatch を通じてデータを直接交換します。
GameKit UI によるマッチメイキング
標準のマッチメイキングインターフェースには、GKMatchmakerViewController を使用します。
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)
}
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)
// 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
)
}
マッチを終了する
すべての参加者の結果を設定してから、マッチを終了します。
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)
}
ターンイベントを受信する
リスナーとして登録します。1つのオブジェクトで複数のGame Centerイベントカテゴリを処理する場合は、GKLocalPlayerListenerを優先してください。
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)
}
}
マッチデータのサイズ
ターンを終了する前に、マッチオブジェクトのmatchDataMaximumSizeを確認してください。サイズの大きな状態は外部に保存し、マッチデータにはコンパクトな参照のみを保持します。
よくある間違い
GameKit APIを使用する前に認証していない
// 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"]
)
}
authenticateHandlerを複数回設定する
// 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
マルチプレイヤーの制限を無視する
// DON'T
func showMultiplayerMenu() { presentMatchmaker() }
// DO
func showMultiplayerMenu() {
guard !GKLocalPlayer.local.isMultiplayerGamingRestricted else { return }
presentMatchmaker()
}
マッチデリゲートをすぐに設定しない
// 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)
}
プログラムで作成したマッチに対してfinishMatchmakingを呼び出さない
// 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)
マッチから切断しない
// DON'T
func returnToMenu() { showMainMenu() }
// DO
func returnToMenu() {
currentMatch?.disconnect()
currentMatch?.delegate = nil
currentMatch = nil
showMainMenu()
}
確認チェックリスト
- アプリ起動時に
GKLocalPlayer.local.authenticateHandlerを1回設定する - GameKit APIを呼び出す前に
isAuthenticatedを確認する - プレイヤーの制限(
isUnderage、isMultiplayerGamingRestricted、isPersonalizedCommunicationRestricted)を確認する - Xcodeの署名設定でGame Center機能を追加する
- App Store Connectでリーダーボードと実績を設定する
- アクセスポイントを設定し、ゲームプレイ中に適切に切り替える
-
GKGameCenterControllerDelegateでgameCenterViewControllerDidFinish内のダッシュボードを閉じる - マッチが見つかったら、すぐにマッチデリゲートを設定する
- プログラムで作成したマッチには
finishMatchmaking(for:)を呼び出し、終了時にはdisconnect()を呼び出してデリゲートをnilにする - ターン制マッチのデータを
match.matchDataMaximumSize以下に保つ -
endMatchInTurnを呼び出す前に、ターン制マッチの参加者に結果を設定する -
GKLocalPlayer.local.register(_:)で招待またはターンのリスナーを登録する - データモードを適切に選択する。状態には
.reliable、頻繁な更新には.unreliableを使用する - すべての非同期GameKit呼び出しでエラーを処理する
参考資料
- ID検証、旧式のボイスチャット、保存済みゲーム、カスタムマッチUI、リーダーボード画像、チャレンジ処理、ルールベースのマッチメイキングについては、references/gamekit-patterns.mdを参照してください。
- GameKitのドキュメント
- GKLocalPlayer
- GKAccessPoint
- GKLeaderboard
- GKAchievement
- GKMatch
- GKTurnBasedMatch