Highlight
Xcode 26 で新たに追加された
.gamekitbundle により、Leaderboards、Challenges、Activities、Achievements の構成情報を App Store Connect の Web 画面からプロジェクトのソースコードに戻すことができ、バージョン管理や Code Review の対象にできます。さらに Game Progress Manager を使えば、ローカルでスコア送信やディープリンク遷移をそのままデバッグ可能です。
主要内容
これまで Game Center を組み込んできた開発者なら、誰もが繰り返しぶつかってきた悩みがあります。Leaderboard、Achievement、Challenge の設定がすべて App Store Connect の Web 画面に散らばっており、ID をひとつ変えるだけでもブラウザにログインしなければなりません。複数人での共同作業には diff がなく、ロールバックは記憶頼り。ローカルでテストを動かすにも、本番サーバーに実データを送り込む必要があります。「新しく追加したステージのスコアが正しくレポートされるか試したい」というだけのことが、ASC でエントリを作って実スコアを送信、ランキングをリフレッシュ、最後にダーティーデータを手で掃除……という手順になりがちでした。
WWDC25 の最初の一手は、まさにここにメスを入れています。Xcode 26 では .gamekit bundle が追加されました。これはローカルファイルで、Game Center のリソースをすべてその中に宣言します。App Store Connect から「Pull」してきて、編集後に「Push」で戻すことができます。同時にローカルには Game Progress Manager が用意され、デバッグ中のスコアはそのセッション内でのみ有効で、本番データを汚染することはありません。つまり Game Center の設定は、ここからコード資産になるということです。PR を通し、Code Review にかけ、ブランチに追従させることができます。
今年のもうふたつの目玉が Challenges と Activities です。Challenges は既存の Leaderboard の上にそのまま乗ります。すでに submitScore() を使っているなら、理論上は一行もコードを書かずに、フレンド同士が時間制限つきで競い合える仕組みを提供できます。Activities はディープリンクの入口で、フレンドからタップして入ってきたプレイヤーを正しいステージやマルチプレイのルームに直接送り届け、Game Center が自動生成する Party Code を使って同じ対戦に集める役割を果たします。
詳細
Game Center の初期化はわずか 2 ステップです。entitlement を追加して、GameKit を一度呼び出すだけ。以下は公式に推奨されている、最も早い初期化タイミングのコードです(04:17)。
GKLocalPlayer.local.authenticateHandler = { _, error in
print("\(GKLocalPlayer.local.alias) is ready to play!")
}
ポイントは次のとおりです。
GKLocalPlayer.localはシングルトンで、本機にログイン中の Game Center プレイヤーを表します。authenticateHandlerはコールバックで、これを設定して初めて GameKit が認証フローを開始します。- handler が呼び出されてからでないと、
GKLocalPlayer.local.aliasは有効な値になりません。それ以前に読むと空文字列です。 - 公式の推奨は、タイトルスクリーンが表示されるタイミングでこのコードを実行することです。早く呼ぶほど、「Top Played Games」の露出枠に入りやすくなります。
Unity 開発者は同じセマンティクスを C# で書きます(04:29)。
var player = await GKLocalPlayer.Authenticate();
Debug.Log($"{player.alias} is ready to play!");
Unity Plugin は GitHub リポジトリ(apple/unityplugins)からソースを取得して自分でビルドします。ビルドが終わると、entitlement が設定済みの Xcode プロジェクトが生成されます。
Challenges に関しては、今回の設計の中で最も控えめな部分と言えます。新規 API の追加を要求しないのです。スコアを送信するコードはそのままで構いません(13:07)。
GKLeaderboard.submitScore(points,
context: 0,
player: GKLocalPlayer.local,
leaderboardIDs: ["thecoast.lb.capecod"])
ポイントは次のとおりです。
leaderboardIDsは配列なので、一度の送信で複数のランキングに反映できます。- 同じ API が、その Leaderboard に紐づいて進行中のすべての Challenge にスコアを自動同期します。
submitChallengeScoreのような専用 API を別途呼ぶ必要はありません。 - 公式が繰り返し強調しているのは、ステージ終了ごとにスコアを 1 回だけ送信し、lifetime のスコアを累積しないことです。Challenge は腕試しであって、廃人プレイ大会ではないからです。
contextは開発者が自由に使える整数フィールドで、ステージ ID や武器構成といった追加の次元を詰め込めます。
Activities はディープリンクをシステムレイヤーまで引き上げました。次のコードは、フレンドからタップして入ってきたプレイヤーを正しいステージに送り届ける入口です(20:24)。
extension AppDelegate: GKLocalPlayerListener {
func player(_ player: GKPlayer, wantsToPlay activity: GKGameActivity) async -> Bool {
let activityId = activity.activityDefinition.identifier
if activityId == "thecoast.activity" {
let level = activity.properties["level"]
if level == "capecod" {
startCapeCod(activity)
}
}
return true
}
}
ポイントは次のとおりです。
GKLocalPlayerListenerはプロトコルで、登録すると GameKit が外部からの起動をすべてこの handler に渡します。activity.activityDefinition.identifierは Xcode の.gamekitbundle 内で手入力した文字列で、リソース側と一致させる必要があります。activity.propertiesは辞書で、Xcode で設定した base properties と、関連リソース(Leaderboard など)の properties をマージした結果が入っています。- handler は
Boolを返して、このリンクの引き継ぎに成功したかをシステムに伝えます。falseを返した場合はシステム側がフォールバック処理を行います。
マルチプレイのゲームを作るなら、Activity には Party Code の仕組みが組み込まれています(22:35)。同じ Code を持つプレイヤーを 1 つの卓に揃えることが可能です。
extension AppDelegate: GKLocalPlayerListener {
func player(_ player: GKPlayer, wantsToPlay activity: GKGameActivity) async -> Bool {
let activityId = activity.activityDefinition.identifier
if activityId == "thecoast.multiplayer" {
startMultiplayer(partyCode: activity.partyCode)
}
return true
}
}
ポイントは次のとおりです。
activity.partyCodeは Game Center が自動生成するもので、独自にルーム番号システムを作る必要はありません。- iMessage で Activity を共有すると、Code が OpenGraph の Web ページに埋め込まれます。受け手側が Games app をインストールしていなくても、共有カードを見ることができます。
- UI 上は現在の Party Code を必ず表示し、手動で入力する手段も用意してください。プレイヤーは別のデバイスやプラットフォームで Code を受け取る可能性があるためです。
ルームのマッチングを自前で実装したくない場合は、Game Center に用意されている matchmaking を 1 行で組み込むこともできます(22:48)。
let match = try await activity.findMatch()
デバッグフローも今回の見どころです。Scheme → Run Options で「GameKit Configuration Debug Mode」にチェックを入れ、ゲームを起動したあと Debug メニューから Game Progress Manager を開くと、.gamekit bundle 内のすべてのリソースがパネルに一覧表示されます。任意のプレイヤーとして任意のスコアを送信でき、Activity のディープリンクも Xcode から直接クリックでトリガー可能です。実機でタップしたのと同じ挙動になります。デバッグデータはすべてそのセッション内にしか存在せず、サーバーに書き込まれることはありません(24:03)。
重要ポイント
-
何をするか: 既存プロジェクトの Game Center 構成を App Store Connect から
.gamekitbundle に移す。- なぜやる価値があるか: 設定がコードと一緒に Git に入り、PR で diff が見える。ロールバックを記憶に頼らずに済み、複数人での作業でもお互いの変更を上書きしません。
- 始め方: Xcode 26 → New File → 「GameKit Bundle」を選択 → ellipsis メニューから「Pull from App Store Connect」を選び、team とゲームを指定すると、リソースがそのままローカルファイルに展開されます。
-
何をするか: 既存の Leaderboard に Challenge を追加し、コードを書かずに「フレンドとの対戦」機能を手に入れる。
- なぜやる価値があるか: 既存の
submitScore呼び出しパスをそのまま流用するためゼロインストルメンテーションです。Challenge は時限と招待 UI が標準装備されており、シングルランキングを一気にソーシャル対戦へと変えます。 - 始め方:
.gamekitbundle 内で対象 Leaderboard に Challenge エントリを追加し、画像、displayName、許容時間(1 日 / 3 日 / 1 週間)を設定します。あとはsubmitScore()のleaderboardIDsが正しく書かれていれば OK です。
- なぜやる価値があるか: 既存の
-
何をするか: よく遊ばれるステージやマルチプレイのルーム向けに Activity のディープリンクを用意する。
- なぜやる価値があるか: フレンドが Games app や iMessage 経由でタップして入ってくると、その場でステージ本編に直行できます。タイトル、メニュー、チャプター選択といった摩擦レイヤーを飛ばせるため、「ゲームを起動してユーザー自身に探させる」よりも明らかに高い遷移率が期待できます。
- 始め方:
.gamekitbundle で Activity を新規作成し、対応する Leaderboard を関連付けてproperties(例:level: "capecod")を設定。GKLocalPlayerListener.player(_:wantsToPlay:)を実装し、identifierとpropertiesでルーティングします。
-
何をするか: Game Progress Manager を開発フローに組み込み、本番サーバーを使ったデバッグを置き換える。
- なぜやる価値があるか: デバッグ中に本番ランキングへダーティーデータを書き込まずに済み、ディープリンクも Xcode から直接クリックでき、任意のプレイヤー名義で送信をシミュレーションできるため、マルチプレイの相互作用シナリオも検証しやすくなります。
- 始め方: Scheme → Run Options を開き、GameKit Configuration Debug Mode にチェック。起動後 Debug メニューから Game Progress Manager を開き、現在のデバイスと自分のゲームを選択します。
関連 Session
- Engage players with the Apple Games app — 新しい Games app の露出メカニズムと申請のポイント
- Level up your games — Apple の統合ゲームプラットフォーム全体の技術マップ
- Discover Metal 4 — Metal 4 の新機能、ゲームレンダリングの基盤アップグレード
- WWDC25 Platforms State of the Union Recap — 今年の開発者カンファレンスの全プラットフォーム概観
コメント
GitHub Issues · utterances