マガジン

親画面とゲームの会話:postMessageで運ばれるものを、保存とプレイヤーIDの図で読む

遊びを見つける

執筆にClaudeを使用しています。

プレイヤーの駒と小さなゲームの世界をつなぐ橋、上に浮かぶ雲形の器
記事のテーマをもとに制作したAI生成イラスト。

ゲームで途中まで進めて閉じ、翌日に別の端末で開くと続きから遊べる場面を考えてみます。ゲームは「いまログインしている人が誰か」を自分では知らないはずです。それなのに、保存データは正しい人の、正しい作品の分だけ戻ってきます。ゲームと、それを包む親画面のあいだで、何かが受け渡されているからです。 ブラウザで「別の窓」へ安全に手紙を送る標準の仕組みが postMessage です。この記事では、アソバースの確認済みの仕様から「何がゲームに渡り、何が渡らないか」を整理し、プレイヤーIDと保存の流れを図にします。ただし、実際のメッセージの名前や項目は確認済み事項に含まれていません。そこは推測で埋めず、読者が自分のブラウザで確かめる手順を示します。

この記事の目次
  1. なぜ会話が必要になるのか
  2. 最小の会話を、手元で再現する
  3. 図1:プレイヤーIDの受け渡し
  4. 図2:保存の往復
  5. 実際の会話を自分の目で確かめる手順
  6. 次の行動
  7. 参考資料

なぜ会話が必要になるのか

アソバースでは、ゲームは sandbox(権限を絞った隔離)付きの iframe で動きます。アップロードされたコードは、メインサイトとは別のオリジンから配信されます。オリジンとは「https://」から始まるアドレスの、スキーム・ホスト・ポートの組です。別オリジンの窓どうしは、お互いの変数や保存領域に直接触れられません。

この仕様から読み取れるのは、ゲームの側から親画面のログイン状態やクラウドへ直接手を伸ばせない構造になっているということです。ゲームは外から預かった情報しか知らず、保存したいときは誰かに依頼するしかありません。隔離した窓どうしをつなぐ橋として、ブラウザの標準手段が postMessage です。送り手が相手の窓へデータを送り、受け手が message イベントで受け取り、そのとき「誰から来たか」を確かめます。

注意点があります。確認済み事項には「プレイヤーIDやクラウドセーブをSDKが扱う」とありますが、SDKと親画面のあいだの通信方式の名前までは書かれていません。この記事では、postMessage を「この構造で最も一般的な橋」として説明します。アソバースの内部実装そのものだとは断定しません。

最小の会話を、手元で再現する

仕組みをつかむには、小さな模型が早いです。次の HTML を .html として保存し、ブラウザで開くと、親と子の会話が見えます。メッセージ名の demo: は、この模型だけの名前です。アソバースの実際の名前ではありません。

html
<!doctype html>
<meta charset="utf-8">
<iframe id="g" sandbox="allow-scripts" style="width:320px;height:80px"
 srcdoc="<script>
 addEventListener('message', function (e) {
   if (e.source !== parent) return;
   document.body.textContent = '子が受信: ' + JSON.stringify(e.data);
 });
 parent.postMessage({ type: 'demo:ask-id' }, '*');
 <\/script>"></iframe>
<pre id="log"></pre>
<script>
 var frame = document.getElementById('g');
 addEventListener('message', function (e) {
   if (e.source !== frame.contentWindow) return;
   log.textContent += '親が受信: ' + JSON.stringify(e.data) + '\n';
   if (e.data.type === 'demo:ask-id') {
     frame.contentWindow.postMessage(
       { type: 'demo:reply', playerId: 'demo-player-1' }, '*');
   }
 });
</script>

読みどころは二つあります。一つ目は、子が起動直後に「IDをください」と親へ送り、親が返事を返す往復です。二つ目は、双方が e.source(送り主の窓)を確かめてから処理する点です。sandbox="allow-scripts" だけの子は、オリジンが「不透明」になり、e.origin が文字列の "null" になります。そのため模型では送り主の窓で照合しています。送り先に '*'(誰宛でも可)を使っているのも、この模型だけの事情です。実際のサービスでは、相手のオリジンが分かるなら、それを指定するのが基本です。

図1:プレイヤーIDの受け渡し

確認済みなのは次の点です。作品ごとに専用のプレイヤーIDがあり、ゲームにはFirebaseのUID・認証トークン・メールアドレスを渡しません。これをメッセージの図にすると、次のようになります。

 ゲーム(別オリジン・sandbox)            親画面(アソバース本体)
        │                                      │
        │ ①「この作品のプレイヤーIDをください」 │
        │ ────────────────────────────────────▶│
        │                                      │ ②ログイン状態を確認
        │                                      │   (親だけが知っている)
        │ ③作品専用のプレイヤーID               │
        │ ◀────────────────────────────────────│
        │   ※UID・トークン・メールは含めない    │

これは概念図です。途中に SDK が挟まるかどうか、メッセージの名前や項目は、確認済み事項にないので描いていません。読み取れるのは役割の分担です。ログインの事実を知るのは親だけで、ゲームに渡るのは作品専用の識別子だけです。

作品ごとに別のIDなら、ある作品で使われたIDから、別の作品での行動を結びつけにくい設計だと読めます。保護者にとっては、ゲームにメールアドレスなどが渡らない境界が明示されているのは安心材料です。一方で、ログインしていないゲストにどんなIDが渡るのかは、確認済み事項にありません。この部分は、作品を開いて確かめる項目です。

図2:保存の往復

保存の確認済み事項は、作品ごとにJSONで128KiBまでのクラウドセーブがあること、アップロード作品ではSDKが自動で追加されること、URL投稿の作品は作者がSDKを読み込むと使えることです。また、未同期の端末セーブはクラウドの古いデータより優先して再送されます。ゲストの進行は端末内に残り、ログイン後のアカウントへ自動では混ざりません。

保存と読み込みを、一続きの図にします。

【保存】
 ゲーム ──(JSON:進行データ)──▶ 親画面 ──▶ クラウド(作品ごと・128KiBまで)
                                  │
                      届かなかったとき:端末に残して後で再送
                      (古いクラウドデータより端末側を優先)

【読み込み】
 ゲーム ──(「保存をください」)──▶ 親画面 ──▶ クラウド
 ゲーム ◀──(JSON:前回の進行)───── 親画面 ◀──

ここから、ゲーム作者に関わる判断基準が三つ出てきます。

  • 保存に入れるのは、JSONにできる素の値(数値・文字列・配列・オブジェクト)だけにします。関数や、自分自身を参照する循環構造は入れられません。
  • 保存データには version のような項目を持たせておくと安心です。公式作品でも、古い保存(version1)が新しい形式(version2)へ移行される仕組みがあります。形式を変える日が来ても、古いデータを読み分けられるからです。
  • 128KiBの上限は「文字数」ではなく「バイト数」で測ります。日本語は1文字が複数バイトになるので、次のように確かめます。
js
var save = { version: 2, stage: 3, best: 1280 };
var bytes = new Blob([JSON.stringify(save)]).size;
console.log(bytes + ' / ' + 128 * 1024 + ' bytes');

アップロード作品の中の localStorage/sessionStorage は、一時メモリーとしての互換処理です。端末やアカウントをまたぐ保存の手段としては頼れません。続きから遊べる作りにしたいなら、SDKのクラウドセーブを使う設計にします。

実際の会話を自分の目で確かめる手順

親とゲームのあいだで何が流れているかは、ブラウザの開発者ツール(DevTools)で調べられます。自作のテスト用ゲームを一つ用意し、次の手順で行います。

  1. ゲームの先頭に次の一行を足し、アップロードまたは公開します。
js
   addEventListener('message', function (e) { console.log('受信', e.origin, e.data); });
  1. 作品を開いた状態で DevTools のコンソールを開き、実行コンテキストの選択欄を、親画面からゲームの iframe に切り替えます。これを忘れると、ゲーム側のログが見えません。
  2. 次の三つの時点で、何が届くかを記録します。
  • 起動直後
  • 保存を実行した直後
  • ページを再読み込みした直後
  1. ログイン中とゲストで同じ操作を繰り返し、届く内容を比べます。

記録する単位は、「時点・e.origin・e.data の型と項目名」の三つで十分です。それぞれ2〜3回繰り返すと、毎回同じかどうかが分かります。

結果の読み方は次のとおりです。

  • 何も届かない場合は、ゲームが先に依頼を送って返事だけを受ける作りか、SDKの内部で処理が閉じていると考えられます。ここで「通信がない」と断定するのは早すぎます。送信側も調べるには、親画面のコンソールで同様の監視を試します。
  • 項目が届く場合は、名前と型をメモします。ただし公開された仕様ではないため、作品がその形に依存すると、将来の更新で動かなくなるおそれがあります。
  • 届いた内容にメールアドレスやトークンらしき文字列が見えたら、確認済みの方針と食い違います。その場合は、問い合わせの窓口に連絡してください。

起こりやすい誤りもあります。

  • e.origin や e.source を確かめずに、届いたデータを信じてしまう。
  • 値を画面共有やスクリーンショットでそのまま公開してしまう。IDは作品専用でも、不用意に晒さないほうが無難です。
  • ゲストの結果とログイン中の結果を、別の作品で比べてしまう。比べるときは、同じ作品・同じ操作にそろえます。

次の行動

まず、手元の HTML で最小の会話を再現し、e.source を確かめる習慣をつけてみてください。次に、テスト用のゲームを一本公開し、起動直後と保存直後に何が届くかを記録してみましょう。メッセージの正確な仕様は、現在の案内と作品側のSDKの説明で確認してください。

参考資料

  • MDN Web Docs「Window.postMessage()」
  • MDN Web Docs「<iframe> の sandbox 属性」
  • HTML Living Standard(Web messaging の章)