マガジン

セーブデータは何バイトか:128KiBの上限はどのくらい余裕があるか

遊びを見つける

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

スマートフォンとノートパソコンの枠を、赤いゲームの駒と雲のアーチがつなぐ切り絵
記事のテーマをもとに制作したAI生成イラスト。

ゲームを作っていて、セーブ処理がひと通り動いたところを考えてみます。次に気になるのは「このセーブ、上限に対してどのくらいの大きさなのか」です。アソバースのクラウドセーブは、作品ごとにJSONで128KiBまでと決まっています。数字としては小さくなさそうですが、「遊んでいるうちに膨らむ」セーブでは、余裕があるかどうかは測るまで分かりません。 この記事では、自分のセーブを実際にバイト数へ直し、上限との距離を見る手順を示します。結果の数字は書きません。測るのは読者自身のセーブです。集計の単位、グラフの描き方、「余裕がある」と判断する目安までを一本の流れにします。 遊ぶ側や保護者の方にも関わる話です。セーブの大きさには、その作品が「何を覚えているか」が表れます。後半では、公式作品の設計から読み取れる範囲で、サイズの天井がどこにあるかも考えます。

この記事の目次
  1. 128KiBを実際のバイト数に直す
  2. 測る道具:数行のコードで十分です
  3. 集計の単位を決める
  4. 最大状態を組み立てる
  5. 増え方を見る:傾きと到達点
  6. 余裕の判断基準
  7. 大きくなりやすい原因と、減らす方法
  8. 起こりやすい誤り
  9. 次の行動
  10. 参考資料

128KiBを実際のバイト数に直す

最初に単位をそろえます。1KiB(キビバイト)は1,024バイトです。1,000バイトのKBとは違います。128KiBは128 × 1,024 = 131,072バイトです。以降はこの数字を上限として扱います。

次に「バイト」と「文字数」の違いです。JSONは文字列なので、保存するときはUTF-8という方式でバイトに直されるのが一般的です。UTF-8では、半角の英数字や記号は1文字1バイトです。ひらがな・カタカナ・漢字の多くは1文字3バイト、絵文字の多くは4バイトです。

たとえば "name":"たろう" は、半角部分に加えて日本語3文字で9バイトを使います。日本語のメッセージや項目名を多く含むセーブは、文字数の見た目よりずっと大きくなります。

もうひとつ確認が要るのは、上限がどの状態のデータに対する数字かです。「圧縮前のJSON文字列のバイト数」なのか、別の数え方なのかは、この記事の根拠にしている確認済みの案内には書かれていません。ここは現在のアソバースの案内で確認してください。確認できるまでは、いちばん厳しく見える「圧縮せずUTF-8にした JSON.stringify の結果」で測るのが安全です。

測る道具:数行のコードで十分です

測るために必要なのは、JSON.stringify で文字列にして、TextEncoder でバイト数を数えることだけです。次のコードは単独のHTMLファイルとして動きます。外部ライブラリもサーバーも使いません。makeSave の中身を自分のセーブ形式に置き換えて、ブラウザで開いてください。

html
<!doctype html>
<meta charset="utf-8">
<pre id="out"></pre>
<script>
const LIMIT = 128 * 1024; // 131072バイト
const size = (v) => new TextEncoder().encode(JSON.stringify(v)).length;

// ① ここを自分のセーブ形式に置き換えます
function makeSave(n) {
  return {
    version: 2,
    settings: { sound: true, lang: "ja" },
    items: Array.from({ length: n }, (_, i) => ({ id: i, x: i % 40, y: i % 25 }))
  };
}

// ② 項目ごとの内訳
function breakdown(save) {
  return Object.entries(save)
    .map(([key, v]) => ({ key, bytes: size(v) }))
    .sort((a, b) => b.bytes - a.bytes);
}

// ③ 数点を測って、1件あたりの増え方を出す
const points = [0, 10, 100, 1000].map((n) => ({ n, bytes: size(makeSave(n)) }));
const a = points[0], b = points[points.length - 1];
const slope = (b.bytes - a.bytes) / (b.n - a.n);
const reach = Math.floor((LIMIT - a.bytes) / slope);

out.textContent = [
  "上限: " + LIMIT + " バイト",
  ...points.map(p => `n=${p.n}: ${p.bytes}バイト (${(p.bytes / LIMIT * 100).toFixed(1)}%)`),
  "1件あたり: " + slope.toFixed(1) + " バイト",
  "上限に届く件数の目安: " + reach,
  "内訳: " + JSON.stringify(breakdown(makeSave(b.n)))
].join("\n");
</script>

ここにあるダミーのセーブ(items の座標データ)は、あくまで手順を動かすための見本です。実際の作品のサイズを表すものではありません。自分の作品のセーブ処理で使っているオブジェクトを makeSave に当てはめるか、実際に保存へ渡す直前のオブジェクトを size() に通してください。

集計の単位を決める

数字を一回出すだけでは、「余裕がある」とは言えません。測る単位を決めて、次の三つを記録します。

  1. 空のセーブ(初期状態):遊び始めの状態です。設定や版番号などの固定部分がここで分かります。
  2. 途中のセーブ:進行を何段階かに区切って、各段階で測ります。ステージ数、置いたものの数、ラウンド数など、作品でいちばん自然な「進み具合の単位」を横軸にします。
  3. 最大状態のセーブ:その作品で積み上がりうる最大の状態です。最重要ですが、実際に遊んで到達するのは大変なので、次の節のように計算で組み立てます。

記録は、1セーブにつき「進み具合の値」と「バイト数」を一行にした表で十分です。最低でも3点は測ります。0、中間、最大に近い点です。同じ条件で3回ずつ測り、中央値を取ると、乱数などの揺れの影響が減ります。

最大状態を組み立てる

「普通に遊んだときの大きさ」よりも、「いちばん大きくなりうるときの大きさ」が、上限との距離を決めます。考え方は次の式です。

最大サイズ ≒ 固定部分 +(件数の上限 × 1件の最大バイト数)

ここで大事なのは、上限のある量と上限のない量を分けて棚卸しすることです。

  • 上限のある量:置けるものの最大数、ステージ数、スロット数など。最大状態を確実に作れます。
  • 上限のない量:プレイ履歴のログ、自由入力の名前やメモ、記録の履歴の追記など。放っておくと増え続けます。

公式作品を、確認済みの範囲で見てみます。「余白の庭」は置けるものが最大80個で、保存があります。この設計から読み取れるのは、庭の配置に関する部分には件数の天井があるということです。ただし、1個あたりに何を保存しているのか、実際に何バイトかは、確認済みの情報にはありません。この部分は作品を開いて確かめる扱いになります。

同じく保存のある「WAVE / WAVE」は16ステップ・4つの音という枠組みがあり、「かたちのあいだ」は盤面が3×3と4×4の2段階です。どちらも「枠が決まっている」ので、最大状態を想像しやすいタイプと言えます。一方、自由入力の文字列や履歴の追記があるセーブは、枠が決まっていないので、自分で上限を設ける必要があります。

自作の場合は、上限のない量にだけ先に「ここまで」という枠を決めてください。履歴なら直近の何件までと決め、古いものを捨てるルールを入れます。これだけで、最大状態を計算で組み立てられるようになります。

増え方を見る:傾きと到達点

測った点を、横軸を進み具合、縦軸をバイト数としてプロットします。手作業の方眼でも表計算でも構いません。見るものは二つです。

固定部分(切片):空のセーブのバイト数です。ここが大きいなら、進行によらず初めから余裕が削られています。

傾き:進み具合が1増えたときに増えるバイト数です。上のコードの slope がこれです。上限に届くまでの目安は、(131,072 − 固定部分) ÷ 傾き で出ます。

仮に、1件が16バイトで増えていく単純な配列があるとします。この仮定の計算では、約8,000件で131,072バイトに近づきます。これは計算の練習用の数字であり、どの作品の実測でもありません。自分の作品では、自分で測った傾きを入れてください。

増え方が直線かどうかも見てください。次のような場合は、直線にならないことがあります。

  • id が1桁から4桁へ増えると、1件あたりの文字数が増える
  • 進行の後半だけ、記録項目が追加される
  • 文字列のメモが長くなる

中間の点を何本か取ると、曲がり方に気づけます。始点と終点だけで引いた傾きは平均にすぎません。

余裕の判断基準

上限に対する使用率で、次の目安を使うことを提案します。これは規則ではなく、この記事での判断のための基準です。作品の性質に合わせて調整してください。

  • 25%未満、読み方:十分な余裕があります、次にすること:版番号を入れ、機能追加後に測り直します
  • 25〜50%、読み方:余裕はあるが、追加機能で近づきます、次にすること:項目ごとの内訳を見て、大きい項目を把握します
  • 50〜75%、読み方:設計の見直しを考える段階です、次にすること:履歴の上限、重複の整理、保存する値の削減を検討します
  • 75%以上、読み方:余裕がありません、次にすること:何を保存するか、根本から選び直します

「最大状態」で測った使用率で判断することがポイントです。途中のセーブだけで25%未満でも、最大状態では75%を超えることがあります。

大きくなりやすい原因と、減らす方法

セーブが膨らむ原因は、たいてい次のどれかです。

  • 画像や音を文字列で入れている:画像を文字列に変換した形式(Base64など)は、元のデータより約3分の1大きくなります。録音した音の波形なども同様です。画像や素材は作品に同梱するファイルとして持ち、セーブには「どの素材を選んだか」という番号や名前だけを入れるのが基本です。
  • 履歴が無制限に増える:直近N件だけ残す、古いものは集計値にまとめる、といった上限を設けます。
  • 同じ値の繰り返し:オブジェクトの配列で、キー名が全件に繰り返し現れます。座標の組なら [x, y] のような配列にすると、キー名の分が減ります。ただし読みにくくなるので、減らす量と可読性を天秤にかけます。
  • 日本語の長い文字列:項目名や説明文そのものを保存せず、番号で持つと小さくなります。

減らしたら、必ず同じ手順で測り直して、減った量を記録します。感覚で「減ったはず」と判断しないことが大切です。

起こりやすい誤り

  • 文字数で数える:text.length は文字数です。日本語や絵文字ではバイト数と大きく異なります。必ず TextEncoder でバイト数にします。
  • 初期状態だけで安心する:最大状態を組み立てずに「まだ余裕がある」と判断してしまいます。
  • 版が変わっても測り直さない:項目を足したら、傾きも固定部分も変わります。セーブに version を入れておけば、形式を変えるたびに測り直すタイミングが明確になります。公式作品でも、古い保存(version1)を新しい形式(version2)へ移行する仕組みが確認済みです。形式を変えるなら、移行の前後でバイト数も比べてください。
  • 個人情報を入れる:ゲームにはFirebaseのUID、認証トークン、メールアドレスを渡さない扱いです。セーブにもこうした値は入れません。作品専用のプレイヤーIDが用意される仕組みなので、ユーザーの識別が必要ならそちらを使います。
  • 保存の仕組みを取り違える:アップロードした作品内の localStorage や sessionStorage は、一時メモリーとしての互換処理です。永続的なセーブとして頼る場所ではありません。クラウドセーブの使い方や、URL投稿の作品でのSDKの読み込みについては、現在の案内で確認してください。

次の行動

まず、自分の作品のセーブ処理で保存に渡す直前のオブジェクトを、上のコードの size() に通してください。次に、空・中間・最大の3点を測り、傾きと使用率を記録します。使用率が50%を超えるようなら、大きい項目の内訳を見て、履歴の上限や画像の持ち方から見直してください。

参考資料

  • MDN Web Docs「TextEncoder」
  • MDN Web Docs「JSON.stringify()」
  • アソバースの現在の案内(クラウドセーブの容量・数え方)