AIゲーム制作ラボ
Unity×AI5分で読めます

Webゲームの進行を保存する(何を保存しないか・壊れても落ちない形)

ブラウザで遊ぶパズルに「どこまでクリアしたか」と自己ベストを保存しました。盤面は保存しません。保存先がブラウザの領域であること、壊れた保存データで落ちない書き方、記事を書いていて気づいた「版が無い」という穴と、その直し方を残します。

Libra

公開 2026年9月19日

この記事の要点4 points

  1. 保存するのは面の番号と自己ベストだけ 盤面は保存しない(面は番号から毎回同じものが作られる)。
  2. だから面の作り方を変えると、同じ番号が別の面になる 保存データの先頭に版を書いて、知らない版は全部捨てる形にした。
  3. 保存データは簡単に壊れる。 読めない部分は捨てて、読めた分だけ使う。落ちるより忘れるほうがまし。
  4. Web の PlayerPrefsそのブラウザの中にしか残らない。 端末をまたぐ記録が欲しいなら、保存先はサーバーになる。
目次6
検証した環境最終確認 2026年9月19日
  • Windows 11
  • Unity 6.6(6000.6.0f1)・WebGL ビルド
  • Chrome(Android の見た目で確認)
  • Claude Code(Opus 5 / 思考レベル 高)

書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。

ブラウザで遊ぶパズルに、進行の保存を入れました。保存するものはこれだけです。

2種類

保存している値

面の番号と自己ベスト

0

保存している盤面

番号から毎回作れる

1行

保存データの中身

v=1;1=8;2=11

36件

通しているテスト

壊れた保存データ込み

盤面を保存しない

このゲームの面は、番号から自動で作られます。面1なら必ず同じ盤、面2なら必ず同じ盤。作るときに使う種は 面の番号 × 7919 と決めているので、誰の端末でも同じ面が出ます。

つまり盤面はいつでも作り直せるので、保存する意味がありません。保存するのは、作り直せないものだけです。

保存するのは、作り直せないものだけ。

保存データはこの形にしました。

v=1;1=8;2=11;3=17

「版が1・面1は8手・面2は11手・面3は17手」という意味です。JSON にしなかったのは、壊れたときに部分的に読めるほうがいいからです。この形なら、真ん中が欠けても前後は読めます。

記事を書いていて気づいた穴:版が無かった

ここまで書いたところで、自分の手落ちに気づきました。最初の版には v=1 が無かったのです。

面は番号から作られます。ということは、面の作り方を変えたら、同じ番号が別の面になる。そのとき古い自己ベストが残っていると、「8手でクリア」と表示されているのに、いまの面はそもそも8手で解けない、ということが起きます。

  1. 最初問題

    `1=8;2=11` だけを保存していた。面の中身が変わることを考えていない

  2. 気づいた要注意

    記事に「面は番号から作られる」と書いた瞬間に、記録が古い面のものになる場合があると気づいた

  3. 直した

    先頭に `v=1` を書き、知らない版なら全部捨てる。版が無い保存データは v1 として読む

版を上げるのは「面の中身を変えたとき」

バグ直しで面が変わらないなら、版は上げません。同じ番号の面が変わるときだけ上げます。上げると古い記録は消えますが、意味の違う記録が残り続けるよりましです。

壊れた保存データで落ちない

保存データは簡単に壊れます。更新の途中、別の版のゲーム、手で書き換えた人。壊れていても落ちないことを先に決めておくと、あとが楽です。

方針は「読めた分だけ使う」。読めない部分は、黙って捨てます。

foreach (var part in data.Split(';'))
{
    var pair = part.Split('=');
    if (pair.Length != 2) continue;                        // 壊れている部分は捨てる
    if (!int.TryParse(pair[0], out int level)) continue;
    if (!int.TryParse(pair[1], out int moves)) continue;
    if (level < 1 || level > TotalLevels || moves <= 0) continue;   // ありえない値も捨てる
    ...
}

テストは、わざと壊した文字列を渡します。

var store = new MemoryProgressStore { Data = "1=8;こわれた;=;3=;x=y;2=11;99999=3;4=-5" };
var progress = new Progress(store);

Assert.That(progress.BestMoves(1), Is.EqualTo(8));   // 読めた分は使う
Assert.That(progress.BestMoves(3), Is.EqualTo(0));   // 読めない部分は捨てる
Assert.That(progress.IsCleared(99999), Is.False);    // 範囲外も捨てる

⚠️ 保存データは「信用できない入力」として扱う

自分のゲームが書いたものでも、読むときは外から来た文字列と同じです。数字だと決めつけない・範囲を確かめる・足りなくても進む。ここを守ると、保存まわりのクラッシュ報告が最初から発生しません。

保存先は「そのブラウザの中」

Unity の PlayerPrefs は、Web ビルドではブラウザの保存領域(IndexedDB)に入ります。ここは覚えておかないと、期待がずれます。

残る

同じブラウザで開き直したとき
タブを閉じても、端末を再起動しても
同じサイト(同じ配信元)の中

残らない

別の端末・別のブラウザ
シークレットウィンドウを閉じたあと
サイトデータを消したとき

端末をまたいで残したいなら、保存先はサーバーになります。2本目のゲームでは、ランキングだけサーバーに置きました(誰でも見える記録なので、そちらが自然です)。3本目の自己ベストは、自分だけのものなので端末に置いています。

本番で、保存して読み直すところまで確かめた

書いたら、公開したものでそのまま確かめます。スマホの見た目のまま面1をクリアし、ページを読み込み直して、記録が残っているかを見ました。

ハコオシの面を選ぶ画面。面1が緑で「5」と表示され、面2が遊べるようになっている。下に CLEARED 1 / 24
読み込み直したあとの画面。面1の下の小さい「5」が自己ベスト、面2が開き、下に CLEARED 1 / 24
Git Bash(自動で開いて、クリア → 読み込み直し → 画面を撮る)OK
errors: なし

まとめ

  • 保存するのは作り直せないものだけ。盤面は番号から作れるので保存しない
  • 自動生成の面を保存と組み合わせるなら、版を書く。面の中身を変えたら版を上げて、古い記録は捨てる
  • 保存データは信用できない入力として読む。読めた分だけ使い、落ちない
  • Web の PlayerPrefsそのブラウザの中。端末をまたぐ記録が要るならサーバー
  • 公開したもので、クリア → 読み込み直しまで確かめる

FAQ

よくある質問

ゲームの進行は何を保存すればいいですか?

作り直せないものだけです。このパズルでは面の番号と自己ベスト(手数)だけを保存しています。盤面は面の番号から毎回同じものが作られるので、保存する必要がありません。

Unity の PlayerPrefs は Web でどこに保存されますか?

ブラウザの保存領域(IndexedDB)です。そのブラウザ・そのサイトの中にだけ残ります。別の端末や別のブラウザには引き継がれず、サイトデータを消すと一緒に消えます。

保存データが壊れていたらどうしますか?

読めた部分だけ使って、読めない部分は捨てます。例外で落ちるより、記録を一部忘れるほうがましだからです。壊れた文字列を渡すテストを置いて、落ちないことを見張っています。

面を自動生成しているとき、保存で気をつけることは?

生成の中身を変えると、同じ番号でも違う面になります。古い自己ベストがそのまま残ると、意味のない記録になります。保存データに版を書いておき、版が違えば捨てるようにしました。

この記事は役に立ちましたか?

NEXT