セーブデータのエクスポートとインポート機能
遊びをつくる執筆にClaudeを使用しています。

ブラウザゲームでセーブデータのエクスポート・インポートを実装するとは、プレイヤーが自分のセーブ内容をファイルとして手元に保存し、別の端末や別のブラウザに持ち込んで続きを遊べるようにすることです。クラウドセーブとは異なり、データの保管場所がプレイヤー自身のストレージになるため、サーバーを用意しなくても「環境を越えたセーブ移行」を実現できます。 この機能を作りたい開発者がまず知りたいのは「何をどの形式で出力すればよいか」「改ざんにどこまで対処するか」「インポート時にバージョンが違ったらどうするか」という3点です。この記事ではその3点を軸に、実装上の判断基準と手順を説明します。
エクスポートで決めるべきこと
エクスポートとは、ゲームが持つセーブ状態をプレイヤーがダウンロードできる形に変換する処理です。まず「何を含めるか」を決めます。
含めるべき典型的な内容は、進行フラグ・所持アイテム・スコア・設定値など「ゲームの再現に必要な最小限のデータ」です。プレイ時間やスクリーンショットのような付随情報は含めると容量が増えるため、必要性を都度検討してください。
出力形式はJSONが最も扱いやすい選択肢です。人間が読めるため不具合の調査がしやすく、JavaScriptとの相性もよいです。バイナリ形式は容量を抑えられますが、デバッグコストが上がります。フォーマットが固まったら、そのJSONをBlobオブジェクトに変換し、<a>タグのdownload属性を使って任意のファイル名でダウンロードさせるのが標準的な実装パターンです。
ファイル名には日付やバージョンを含めておくと、プレイヤーが複数のバックアップを管理しやすくなります。たとえば save_v2_20261020.json のような命名規則は導入コストがほぼゼロで、後からのサポート対応を減らす効果があります。
インポートの実装と入力検証
インポートはエクスポートより注意点が多いです。プレイヤーが外部から持ち込むファイルは、作者が意図しない内容を含む可能性があります。
最初に行うべきはスキーマ検証です。受け取ったJSONが想定するプロパティと型を持っているかをチェックします。たとえば gold フィールドが数値であるべきところに文字列が入っていたら拒否する、level が想定範囲外の整数なら上限にクランプするといった処理を加えます。検証ライブラリを使う場合はZodやAjvが代表的ですが、軽量なゲームであれば手書きのif分岐で十分なケースも多いです。
次に考えるのはバージョン差異への対応です。ゲームをアップデートするとデータ構造が変わる場合があります。エクスポートファイルにバージョン番号を必ず含め、インポート時にそのバージョンを読み取って変換処理(マイグレーション)を走らせる設計にしておきます。たとえばv1のセーブにはなかった stamina フィールドをv2で追加した場合、v1ファイルを読み込んだときはデフォルト値を補完してから使うコードを書きます。この対応を最初から仕込んでおかないと、アップデートのたびに過去のセーブが壊れてサポート対応が発生します。
インポートのUI上の注意点として、上書きの確認ダイアログを入れてください。操作ミスで現在のセーブが消えるのはプレイヤーにとって最悪の体験です。「現在のセーブを上書きします。よろしいですか?」という1ステップを挟むだけで防げます。
改ざん対策をどこまでやるか
エクスポートしたJSONはテキストファイルなので、プレイヤーが直接編集して数値を書き換えることができます。これをどこまで防ぐかは、ゲームの性質によって方針が変わります。
シングルプレイのみのゲームで、ランキングも他プレイヤーへの影響もない場合は、改ざんを厳密に防ぐ必要はありません。多少の改ざんはプレイヤー自身の遊び方として許容できます。
スコアランキングや対戦要素がある場合は、改ざんされたセーブを使ってランキングに登録されると公平性が崩れます。この場合、インポート経路ではランキングへの書き込みを禁止する、もしくはインポートデータを「参照専用」として扱い、正規のゲームプレイによって上書きされるまではランキング提出を無効にする設計が有効です。
技術的な対策としてはHMAC署名(データとサーバー側の秘密鍵を組み合わせたハッシュ)がありますが、ブラウザゲームで秘密鍵を安全に持つのは難しく、過信は禁物です。「フロントエンド単体での完全な改ざん防止は難しい」という前提に立ち、ゲームデザインの側でリスクを下げる発想が現実的です。
ローカルストレージとの使い分け
通常のセーブはブラウザのlocalStorageやIndexedDBに保存しているケースが多いです。エクスポート・インポートはそれらのデータを補完する手段であり、置き換えるものではありません。
両者の役割分担はシンプルに考えられます。localStorageはゲームを開いた瞬間に自動で読まれる「日常のセーブ場所」、エクスポートファイルはブラウザをまたいだ移動やバックアップのための「可搬な複製」です。
エクスポート機能は、localStorageの現在の中身をそのままJSONに書き出すだけで成立します。インポート機能は受け取ったJSONを検証してlocalStorageに書き戻す処理です。この2方向の橋渡しが実装の本質です。
アソバースのSDKを使ってクラウドセーブと連携している場合、エクスポートの対象をクラウドセーブのデータにするかlocalStorageにするかを設計段階で決めておく必要があります。どちらを「正」にするかが曖昧なままだと、インポート後の同期処理で予期しない上書きが起きることがあります。
次の行動
まずエクスポートから着手し、出力するJSONにバージョン番号フィールドを必ず入れてください。インポート実装時はスキーマ検証と上書き確認ダイアログを最優先で組み込み、改ざんリスクへの方針はゲームのランキング設計と合わせて決めます。完成したらアソバースにZIPまたはHTMLとして投稿し、実際に別ブラウザでセーブ移行を通して確認してみてください。

