Skip to content

OTA アップデート

すでにインストール済みのプレイヤーに向けて、エンジン JS・ゲームコード・アセットを含む新しいバージョンを、ストア審査なしで届けられます。Modoki のゲームは WebView 上で動作するため、アップデートはアプリが描画するあらゆるものを差し替えられます。ネイティブ部分だけは対象外です。

差し替えられるもの・差し替えられないもの

差し替えられる: エンジンコード、ゲームの TypeScript/JS、シーン、そしてすべてのアセット(テクスチャ、モデル、オーディオ、フォント)。これは通常のビルドが生成するのと同じ内容です — OTA は、それをストアを経由せず、すでにインストール済みのアプリへ届けるだけです。

差し替えられない: ネイティブ部分すべて。新しい Capacitor プラグイン、変更されたネイティブ権限、更新されたアプリアイコン、引き上げられた最小 OS バージョン — これらはすべて実際のストアリリースが必要です。変更が WebView の中だけで完結するなら OTA で運べますが、WebView を包むネイティブシェルに触れるなら運べません。

一度だけのセットアップ

アップデートを公開する前に、プロジェクトには署名用の鍵と、バンドルを配置する場所が必要です。

  1. 署名鍵を生成するBuild → OTA Keys… を開き、名前を選んで(default のままでも構いません)Generate をクリックします。これにより秘密鍵がお使いのマシン上の build/ota-keys/<name>.json に書き込まれ、対応する公開鍵が表示されます。

    バックアップを取ってください — 「再生成」はできません

    秘密鍵はお使いのマシンから外に出ることはなく、git にコミットされることもありません。これを失うと、すでに配布済みのアプリバイナリは二度とアップデートできなくなります — それらには古い公開鍵が焼き込まれており、新しい鍵で署名されたものを拒否するためです。ダイアログ(および裏側のツール)は、まさにこの理由から既存の鍵ファイルの上書きを拒否します。2 つ目の識別子が必要な場合は、別の名前で新しく生成してください。既存のものを「リセット」することはできません。

  2. バケットを用意する — OTA バンドルは CDN 付きバケット上の静的ファイルとしてホストされます(このガイドでは、組み込みの公開ツールが対象とする Google Cloud Storage を前提とします)。バケットは公開読み取り可能で、かつ GET/HEAD に対する CORS が有効である必要があります — CORS がないと、公開自体は成功したように見えても、各デバイスのアップデート取得用 fetch() が静かに失敗し続けます。Build → Publish OTA Update… は公開のたびにこれを確認・設定してくれるので、手動で設定する必要はありません。

  3. Project Settings → OTA を入力するEnabled をオンにし、Base URL にバケットの公開 URL を設定し、Bundle name(メインアプリなら shell)を確認します。Public key フィールドは読み取り専用です — 手順 1 で鍵を生成すると自動的に入力され、手入力することはありません。

アップデートを公開する

セットアップが済めば、公開は 1 つのダイアログで完結します: Build → Publish OTA Update…。現在何が公開中かを表示し、次のバージョン文字列を自動入力し、Mandatory チェックボックスを提供します。Publish をクリックすると、進捗がストリーミング表示されます: 現在のプロジェクト設定からの新規ビルド、そのバージョン文字列がまだ公開されていないことの確認、そしてアップロードです。

これだけです — 事前に別途「ビルド」を行う必要はありません。ダイアログは常にディスク上の現在の状態からビルドするので、エディタで見ているものがそのまま配信されます。

プレイヤー側の体験

公開したアップデートは、すでにプレイ中のプレイヤーに即座には適用されません — バックグラウンドでステージングされ、次回起動時に反映されます。これは意図的な設計です。実行中のセッションの下でコードを差し替える方が、再起動を求めるよりもはるかにリスクが高いためです。

  • Routine(既定): 静かにステージングされます。プレイヤーは次にアプリを開くまで何も気づかず、開いた時点ですでに新しいバージョンになっています。
  • Mandatory: プレイヤーにはダウンロード中の進捗バーが表示され、その後「続けるには再起動してください」という画面になります。iOS ではアプリが自分自身を再起動できないため、これはバグではなく意図された行き止まりです — プレイヤーはアプリを閉じて開き直すことで続行します。

Mandatory は控えめに使ってください — 公開した瞬間、プレイ中の全員を中断させます。これはゲームを壊すバグやセキュリティ修正など、今すぐ届けなければならないアップデートのためのものであり、通常のコンテンツ向けではありません。

アップデートがうまくいかなかったとき

すべてのアップデートは、デバイスに信頼されるまでに、2 回の別々のアプリ起動で連続して正常に起動する必要があります。3 回連続で起動に失敗すると、デバイスは自動的に直前の正常だったバージョンに戻り、壊れていたバージョンを永続的に拒否済みとしてマークします — プレイヤーがクラッシュループを目にすることはなく、壊れたアップデートに固定されることもありません。

復旧は「前進あるのみ」

あるバージョンがデバイス上で一度拒否されると、そのデバイスは二度とそれを試しません — 同じバージョン文字列で再公開しても、何度やっても同じです。復旧する唯一の方法は、新しいバージョン番号を公開することです。 すでに公開したのと同じ文字列で修正版を再公開すると、すでに拒否済みのデバイスはすべて固定されたままになります — 公開自体はうまくいったように見えて、実際に修正が必要なプレイヤーには静かに届きません。一度公開したバージョン文字列を再利用しないでください。

ストアポリシー

Apple も Google も、一定の範囲内でこれを許可していますが、ここに書かれた要約に頼らず、公開前に必ず現行のガイドラインを自分の目で確認してください。ポリシーの文言は変わり得るためです。

  • AppleApp Store Review Guideline 2.5.2 は、アプリが自己完結していることを求めていますが、WebKit/JavaScriptCore によって解釈されるコンテンツについては、アプリの主目的を変えない限りという明示的な例外があります。既存のゲームに新しいレベルや修正、アセットを届けることはこの例外に当てはまりますが、OTA を使って無関係なアプリや汎用のアプリストアをアプリ内に組み込むことは当てはまりません。
  • Google Play — 「アップデート頻度」に相当する制限はありませんが、WebView / JavaScript コンテンツに関するポリシーは引き続き適用されます。実行時に任意のサードパーティ JavaScript を読み込まないこと、アップデートの仕組みが何をしているかを開示することが求められます。

要するに、これは自分のゲームのコンテンツや修正を届けるために使うものであり、本来なら承認されないものをストア審査から回避するために使うものではありません。

トラブルシューティング

OTA の失敗のほとんどは意図的に静かです(アップデートチェックが、プレイヤーが今まさに見ているゲームをクラッシュさせてはならないため)。そのため、症状だけが手がかりになります。

症状想定される原因
公開は「成功」するが、どのデバイスもアップデートを報告しないバケットに CORS が設定されていません。Publish OTA Update… を再実行してください — 公開のたびに CORS を確認・設定します。
アップデートが常に小さな差分ではなく、バンドル全体をダウンロードするアプリ自身のビルドに埋め込みマニフェストが含まれていません(古いネイティブビルド、あるいは Android 特有の Gradle アセットマージの古さが原因です。OTA 対応のパイプラインでリビルドしてください — OTA が有効なプロジェクトではクリーンビルドが実行されます)。これは正しさの問題ではありません — 差分転送は最適化に過ぎないため、帯域を余分に使うだけでアップデート自体が壊れることはありません。
バージョンを再公開したのに、一部のデバイスが古いコードのまま動いているバージョン文字列を再利用しています。上記「復旧は『前進あるのみ』」を参照し、新しいバージョン番号を公開してください。
公開したアップデートが、古いビルドをインストール済みのプレイヤーにまったく届かないProject Settings の Base URL/Bundle name/Public key が、それらのインストール済みビルドが実際に出荷された時の値と一致しているか確認してください — ここが一致していないと、アップデートが見つからないのと同じように静かに失敗します。

関連

Modoki で構築。