保存データの差分:全部保存しないで済む考え方
遊びをつくる執筆にClaudeを使用しています。

16ステップ×4つの音を並べるリズムのグリッドを考えてみます。全部で64マスあり、そのうち音が入っているのは数マスだけだったとします。保存するたびに64マス分の値をすべて書き出すのは、ほとんどが「何も入っていない」という同じ情報の繰り返しになります。「変わったところだけ」を書けば、記録は短くなりそうです。 では、全部保存をやめて差分だけにすれば、いつでも得をするのでしょうか。この記事では、基準になる状態と変更の一覧を組み合わせる「差分保存」の仕組みを、小さなコードで確かめます。そのうえで、差分が得をする場面と損をする場面、壊れやすいところの見分け方を整理します。 なお、アソバースの公式作品が内部でどんな形式を使っているかは、確認済みの情報にありません。作品の仕様として確かなことと、設計から読み取れることを分けて書きます。細部は読者自身が作品を開いて確かめる形にします。
全部保存と差分保存の違い
「全部保存」は、今の状態をそのまま丸ごと書き出す方法です。読み込むときは書かれた内容をそのまま状態に戻すだけなので、考えることが少なく、壊れにくいのが長所です。
「差分保存」は、次の二つを組み合わせます。
- 基準:誰もが同じものを再現できる出発点の状態です。たとえば「64マスすべてが空」という初期状態です。
- 変更の一覧:基準から何が変わったかだけを並べたものです。「20番目のマスを1にする」のような記録です。
読み込むときは、基準を用意し、変更を上から順に当てはめます。保存した内容そのものが状態なのではなく、「基準+変更」を計算した結果が状態になります。ここが全部保存との大きな違いです。
ここで、基準が必ず再現できなければならない点に注意してください。基準が保存側と読み込み側で食い違うと、同じ変更を当てても違う状態になります。差分保存の難しさの大半は、この基準の扱いにあります。
差分で記録するときの最小の形
先ほどの64マスのグリッドで、差分の作り方と復元の仕方を最小のコードで書きます。HTMLファイルとして保存してブラウザで開き、開発者ツールのコンソールを見ると動作を確かめられます。
<script>
// 16ステップ×4トラック = 64マス。0は空、1は音あり
const base = Array(64).fill(0);
// 基準と現在の状態を比べ、違うマスだけを [番号, 値] で返す
function diff(a, b) {
const out = [];
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) out.push([i, b[i]]);
}
return out;
}
// 基準に変更を順に当てはめて状態を復元する
function apply(baseState, changes) {
const s = baseState.slice();
for (const [i, v] of changes) s[i] = v;
return s;
}
// 現在の状態を作る(3マスだけ音を入れる)
const now = base.slice();
now[0] = 1; now[20] = 1; now[37] = 1;
const changes = diff(base, now);
const full = JSON.stringify({ version: 2, cells: now });
const delta = JSON.stringify({ version: 2, changes });
console.log("全部保存の文字数:", full.length);
console.log("差分保存の文字数:", delta.length);
console.log("復元が一致するか:",
JSON.stringify(apply(base, changes)) === JSON.stringify(now));
</script>このコードのポイントは三つあります。
- diff は、基準と現在を同じ長さの配列として並べ、違う位置だけを拾います。
- apply は、基準のコピーに変更を当てるだけです。元の base を書き換えないように slice() でコピーしています。
- 最後の行で「復元した状態が元の状態と一致するか」を確かめています。差分保存では、この往復テストを必ず入れておくと安心です。
保存データに version を入れているのも意図的です。基準や変更の書き方を将来変えたとき、古いデータを見分けて新しい形式へ移すために使えます。
差分が得をする場面、損をする場面
差分は万能ではありません。変更を一つ書くには「どこを」「何にする」の二つの情報が必要で、全部保存の一マス分より長くなります。したがって、変わった数が多くなるほど得は小さくなります。
- 変更が少ない状態、全部保存:空きの繰り返しが多く長くなりやすい、差分保存:短く済みやすい
- ほとんどのマスが変わった状態、全部保存:長さは一定、差分保存:変更1件あたりが長く、全部保存より大きくなることがある
- 読み込みの単純さ、全部保存:そのまま戻すだけ、差分保存:基準を用意して変更を当てる必要がある
- 基準が変わったとき、全部保存:影響を受けにくい、差分保存:古い変更が当てはまらなくなる恐れがある
- 途中の履歴を残したいとき、全部保存:毎回丸ごとなので重い、差分保存:変更を並べるだけで履歴になる
表の最後の行は、差分の別の使い道を示しています。変更を順番に並べたものは、そのまま「操作の履歴」になります。最後の変更を取り除けば一手戻り、取り除いた変更を別の置き場に取っておけばやり直しができます。保存容量の節約だけでなく、「戻す」「やり直す」と同じ考え方で扱える点が、差分の面白いところです。
損をしやすい典型は、全面的に作り替えるタイプのデータです。たとえば毎回すべてのマスが変わるなら、差分にする意味はほとんどありません。変更の割合を見て、方式を選ぶ必要があります。
公式作品とアソバースの保存から読み取れること
ここからは、確認済みの事実と、そこから読み取れる設計の考え方を分けて見ていきます。作品の内部実装が差分かどうかは確認済みの情報にないため、断定しません。
余白の庭
確認済みなのは次の点です。木・石・草・花・池・灯りを置けること、最大80個であること、移動や大きさ調整ができること、戻す/やり直す(Z/Y)があること、保存があり以前の庭を引き継げることです。
「置いたものの数に上限がある」「操作を戻したりやり直したりできる」「保存して引き継げる」という組み合わせは、庭を一つ一つの操作や配置物の集まりとして扱う設計と相性がよいものです。ただし、内部が配置物の一覧なのか、操作の履歴なのか、その両方なのかは、この記事では判断できません。作品を開き、置く・戻す・やり直す・保存して開き直すを順に試して、自分の目で確かめてください。
かたちのあいだ
確認済みなのは、一手戻すがあること、戻しても手数は減らないこと、同じ配置の再挑戦ができること、保存があることです。
「戻しても手数は減らない」という仕様は、盤面の状態と、操作した回数の記録を別々に扱っていることを示しています。盤面だけ見れば一手前に戻っていますが、回数は「実際に動かした回数」として残っています。差分保存を考えるときも、「状態を復元するための情報」と「記録として残したい情報」は別物として設計する、という手がかりになります。「同じ配置の再挑戦」が何を基準に動くのかも、プレイして確かめる価値があります。
アソバースのセーブ
アソバース側で確認済みなのは、次の点です。
- 投稿したゲームはSDKを通じて、作品ごとにJSONで128KiBまでのクラウドセーブを使えます。
- 未同期の端末セーブは、クラウドの古いデータより優先して再送されます。
- ゲストの履歴や進行は端末内に保存され、ログイン後のアカウントへ自動では混ざりません。
- 公式作品では、古い保存(version1)は新しい形式(version2)へ移行します。
ここから、差分保存を考える投稿者が気をつけるべきことが読み取れます。
- 容量の上限はJSON全体に対して128KiBです。 小さなゲームなら、全部保存でも収まることは多いはずです。差分にするかどうかは、まず測ってから決めるのが筋です。
- 端末とクラウドで内容が食い違う場面が想定されています。 差分保存では、「どの基準に対する変更なのか」が食い違うと復元できません。基準の識別子(version番号や基準の名前)を保存データに入れておくと、食い違いに気づけます。
- 形式の更新(version1→2)が現実に起こります。 差分の書き方を変えたときに、古いデータを新しい形へ変換する手順を最初から用意しておくと、プレイヤーの進行を失わずに済みます。
自分で確かめる手順
差分が自分のゲームに向いているかは、次の手順で数字として確かめられます。結果は、ゲームごとに変わります。
- 保存したい状態を決めます。 たとえば「盤面」「配置物の一覧」「進行度」など、実際に保存するデータだけを対象にします。
- 全部保存の大きさを測ります。 保存するオブジェクトを JSON.stringify で文字列にし、文字数を記録します。日本語を含む場合は、バイト数と文字数が違う点に注意してください。上限の128KiBはバイト単位です。
- 典型的な遊び方を3種類用意します。 序盤・中盤・終盤など、変化の量が違う状態を選びます。
- それぞれの状態で、基準との差分を作って大きさを測ります。 全部保存と並べて、どちらが小さいかを1件ずつ記録します。
- 復元の往復テストをします。 「基準+変更」で復元した状態が元と一致するかを、毎回確認します。
- 読み方を決めておきます。
- どの状態でも差分が小さいなら、差分保存に向く可能性があります。
- 終盤で差分が全部保存を上回るなら、一定量を超えたら全部保存に切り替える方法を検討します。
- そもそも全部保存が上限に遠く及ばないなら、無理に差分にしないほうが安全です。
起こりやすい誤りは三つあります。
- 基準になる初期状態を、あとからゲームの更新で変えてしまうこと。
- 変更の適用順序を保存せず、同じ場所への変更が重なったときに結果が変わること。
- 復元のテストをせず、読み込みの失敗に保存後に初めて気づくこと。
次の行動
まず、いま作っている(または作りたい)ゲームの保存データを JSON.stringify で書き出し、文字数を測ってみてください。次に、上のコードを自分のデータ構造に置き換えて、全部保存と差分保存の大きさを3種類の状態で比べます。最後に、余白の庭やかたちのあいだを開き、戻す・やり直す・保存して開き直すの動きを観察して、自分の設計に取り入れたい点をメモしてみましょう。

