マガジン

「ゲームの中のlocalStorageは使えますか」:互換処理の範囲

遊びを見つける

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

机上の二つの端末の間に、小さなゲームの駒と記録カードが並ぶイラスト。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

結論から答えます。アソバースにアップロードした作品の中でlocalStorageやsessionStorageを呼び出しても動くように、互換処理が用意されています。ただし、その中身は「一時メモリー」です。ブラウザ本来の保存と同じものとして、次の起動に値が残ることを当てにしてはいけません。 残したい進行や記録がある場合は、クラウドセーブを使います。アップロードした作品にはSDKが自動で追加され、作品ごとにJSONで128KiBまで預けられます。ログインしている利用者は、この仕組みで進行を引き継げます。 この記事は、この結論を「作る人が自分の作品で確かめる手順」に落とします。互換処理が実際にどこまで値を保つかは、環境や時期で変わりえます。そのため結果を断定せず、小さなHTMLで観察し、結果をどう読めば設計の判断になるかを示します。

この記事の目次
  1. 「使えますか」は二つの質問に分かれます
  2. 互換処理が置かれている環境から読み取れること
  3. 自分の作品で確かめる手順
  4. 結果の読み方と、起こりやすい誤り
  5. 残したいデータは、クラウドセーブに預ける
  6. 次の行動

「使えますか」は二つの質問に分かれます

「使えますか」という問いには、二つの意味が混ざっています。

  1. 呼んでもエラーにならず、書いた値を同じ画面の中で読み戻せるか。 既存のゲームをそのまま持ち込んだときに、起動直後に止まらないかという話です。
  2. 次に開いたときにも値が残っているか。 最高記録やセーブデータを預けられるかという話です。

確認済みの案内で言えるのは、アップロード作品内のlocalStorageとsessionStorageは「一時メモリーの互換処理」だということです。一つ目の意味では、既存コードを動かすための橋渡しとして用意されています。二つ目の意味では、残ることを約束するものとしては案内されていません。

アソバースの公式作品13本にも、同じ考え方が見えます。保存があるのは「余白の庭」「WAVE / WAVE」「かたちのあいだ」の3作品だけです。残る10作品の記録や録音は、画面を開いている間だけ残ります。たとえば「こだまの森」の最高記録は、ゲームを開いている間だけです。「雨音のピアノ」の録音も同じです。保存するかしないかを、作品ごとに明示的に決めている形です。

互換処理が置かれている環境から読み取れること

作品がどんな環境で動くかについて、確認済みの事実は次のとおりです。

  • ゲームはsandbox付きのiframeで実行されます。
  • アップロードしたコードは、メインサイトとは別のオリジンから配信されます。
  • 直下のindex.htmlが入口で、サーバー用コードやサービスワーカーは対象外です。

ここから読み取れるのは、作品がブラウザ標準の保存領域を前提にしていないということです。制限のかかった別オリジンの枠の中で動くため、ブラウザ側の保存が使えるかどうかは、作品の側からは保証できません。互換処理は、保存が使えない場合でも、既存のゲームを止めずに動かすための仕組みだと考えられます。ただし、これは確認済みの事実から見た読み取りです。互換処理の内部の挙動や、どのタイミングで値が消えるかといった細部は、現在の案内で確認してください。

一方で、残す仕組みは別に用意されています。

  • アップロードした作品には、SDKが自動で追加されます。
  • URL投稿の作品は、作者がSDKを読み込むと、作品専用のプレイヤーIDとクラウドセーブを使えます。
  • クラウドセーブは作品ごとにJSONで128KiBまでです。

つまり、アップロード作品では「一時的な値はlocalStorage互換で、残す値はSDK」という役割分担が想定されていると読めます。SDKの具体的な関数名や呼び出し方は、この記事では扱いません。現在の案内で確認してください。

自分の作品で確かめる手順

自分の環境で何が起きるかは、小さなHTMLを一つ投稿して観察すると確かめられます。次のコードは、起動回数を数えてlocalStorageとsessionStorageに書き、結果を画面に出す最小構成です。

html
<!doctype html>
<meta charset="utf-8">
<title>storage-check</title>
<pre id="out"></pre>
<button id="plus">+1</button>
<script>
const out = document.getElementById("out");
function safe(fn) {
  try { return fn(); } catch (e) { return "error:" + e.name; }
}
function show() {
  const lines = [];
  for (const [name, store] of [["local", localStorage], ["session", sessionStorage]]) {
    const v = safe(() => store.getItem("count"));
    lines.push(name + " count = " + v);
  }
  lines.push("opened at " + new Date().toLocaleTimeString());
  out.textContent = lines.join("\n");
}
function bump() {
  for (const [name, store] of [["local", localStorage], ["session", sessionStorage]]) {
    const n = Number(safe(() => store.getItem("count"))) || 0;
    const r = safe(() => { store.setItem("count", String(n + 1)); return "ok"; });
    if (r !== "ok") out.textContent += "\n" + name + " write: " + r;
  }
  show();
}
document.getElementById("plus").addEventListener("click", bump);
show();
</script>

このコードは、書き込みや読み出しで例外が出ても止まらず、画面に理由を出します。ブラウザ標準の保存が制限された環境でも、エラー名を観察できます。

記録の取り方

次の6つの操作を、それぞれ1回ずつ行い、画面の表示を書き留めます。

  1. 開いた直後の表示を記録します。
  2. 「+1」を3回押し、local と session の値を記録します。
  3. 作品のページを再読み込みし、値を記録します。
  4. 別の作品ページを開いてから戻り、値を記録します。
  5. タブをいったん閉じ、同じ作品を開き直して値を記録します。
  6. ログイン中とログインしていない状態の両方で、1〜5を繰り返します。

記録の単位は「操作ごとの local と session の値」です。余裕があれば、別の端末やブラウザでも同じ順に試し、同じ表にまとめます。1回の結果だけで判断せず、同じ手順を2回以上繰り返して、毎回同じかどうかも確認します。

結果の読み方と、起こりやすい誤り

観察結果は、次のように読みます。

  • 手順2で値が増え、手順3で0に戻る、読み方:一時メモリーとして動いています。同じ画面の中だけで使う値には問題ありません。
  • 手順2で値が増え、手順3以降も残る、読み方:その環境ではたまたま保たれています。残る約束とは別なので、依存しないでください。
  • 書き込みでエラー名が出る、読み方:保存が使えない環境があります。例外処理を入れ、失敗しても進行できるようにします。
  • ログイン有無や端末で結果が違う、読み方:環境差があります。残る前提のデータ設計は避けます。

とくに注意したいのは、二行目のパターンです。自分の環境で値が残って見えると、「保存できている」と思い込みがちです。しかし、案内されているのは一時メモリーの互換処理です。別の端末や別の日に同じ結果になるとは限りません。「手元では残った」は、設計判断の根拠にはなりません。

そのほかに起こりやすい誤りがあります。

  • 例外処理を書かない。 保存の呼び出しが失敗したときにゲーム全体が止まると、遊べなくなります。
  • 一時メモリーに最高記録を置いたまま、「保存されます」と説明に書く。 読者に誤解を与えます。残らない場合は、「画面を開いている間だけ」と明記する方法があります。公式作品の10本はこの扱いです。
  • ローカルで開いた結果をそのまま信じる。 ファイルをブラウザで直接開く場合と、アソバース上のiframeで動く場合は、条件が違います。アップロード後の表示で確かめてください。

残したいデータは、クラウドセーブに預ける

残したい値は、クラウドセーブに預けます。確認済みの条件を整理します。

  • 作品ごとにJSONで128KiBまで保存できます。
  • アップロード作品にはSDKが自動で追加されます。
  • URL投稿の作品は、作者がSDKを読み込むと使えます。
  • 作品専用のプレイヤーIDが使われます。ゲームにFirebaseのUID、認証トークン、メールアドレスを渡してはいけません。
  • 未同期の端末セーブは、クラウドの古いデータより優先して再送されます。
  • ゲストの履歴や進行は端末内に保存され、ログイン後のアカウントへ自動では混ざりません。

この条件から、設計で気をつける点が見えてきます。まず、セーブデータは128KiB以内に収める必要があります。次のコードで、保存したいオブジェクトのバイト数を概算できます。

js
const save = { level: 3, items: ["a", "b"], best: 1280 };
const bytes = new Blob([JSON.stringify(save)]).size;
console.log(bytes + " / " + 128 * 1024 + " bytes");

JSON.stringify(save).length は文字数で、日本語を含むとバイト数と一致しません。上限の比較には、Blobのsizeを使うほうが確実です。

次に、ゲストで遊んだ進行はログイン後に自動では移りません。「ログインすると引き継げる」と書く場合は、実際の挙動を現在の案内で確かめてからにします。どの値をクラウドに預け、どの値を一時メモリーに置くかは、データごとに次のように分けて考えられます。

  • 残したい: 到達ステージ、解放済みの要素、自己ベスト、設定
  • 残さなくてよい: 今の盤面、画面の拡大率、直前の操作履歴

残さなくてよい値は、localStorage互換の一時メモリーに置いても困りません。残したい値だけをSDKに預ければ、保存の設計が簡潔になります。

なお、クラウドセーブの呼び出し方、保存のタイミング、失敗時の挙動は、この記事では扱いません。現在の案内で確認してください。

次の行動

まず、上の確認用HTMLを非公開か下書きの状態で投稿し、6つの操作を記録してください。結果の表を見て、自分の作品の値を「一時でよいもの」と「残したいもの」に分けます。残したいものは、現在の案内でSDKの使い方を確認し、128KiB以内に収まるかをBlobで測ってから預け先を決めます。