Unityで学ぶセーブ機構:JSONでゲームデータを一括保存・読み込み(入門〜実践)
目次
なぜJSON保存?
PlayerPrefsは「少量の設定値」を保存するのに便利ですが、データが増えるとキーの管理が大変になります。
JSON保存にすると——
- ひとつのファイルに構造化データとしてまとめられる
- フィールド追加・削除など拡張がしやすい
- デバッグ時に中身が読める(テキスト)
セーブ先の場所
モバイル/PC問わず安全な保存先は Application.persistentDataPath。
例)/Users/…/AppData/LocalLow/<Company>/<Product>/(Windows)
using System.IO;
using UnityEngine;
public static class SavePaths
{
public static string Dir => Application.persistentDataPath;
public static string File => Path.Combine(Dir, "save.json");
}
1. データ構造(POCOクラス)を定義
Unity標準の JsonUtility は publicフィールド を対象にします(プロパティは非対応)。
将来の互換性のために バージョン を持たせておくと便利です。
[System.Serializable]
public class GameData
{
public int version = 1; // マイグレーション用
public int level = 1; // 1〜99
public float score = 0f;
public string item = "なし";
}
2. セーブ/ロードの実装(JsonUtility版)
セーブ(アトミック書き込み)
一時ファイルに書いてから置き換えると、クラッシュ時でも破損しにくくなります。
using System.IO;
using UnityEngine;
using System.Text;
public static class JsonSaveSystem
{
public static void Save(GameData data)
{
if (data.level < 1 || 99 < data.level)
throw new System.Exception("レベルは1~99で指定してください");
Directory.CreateDirectory(SavePaths.Dir);
string json = JsonUtility.ToJson(data, prettyPrint: true);
// アトミックっぽく: temp -> move
string tmp = SavePaths.File + ".tmp";
File.WriteAllText(tmp, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier:false));
if (File.Exists(SavePaths.File)) File.Delete(SavePaths.File);
File.Move(tmp, SavePaths.File);
Debug.Log($"Saved: {SavePaths.File}\n{json}");
}
public static GameData LoadOrDefault()
{
if (!File.Exists(SavePaths.File))
{
Debug.Log("セーブが見つからないため初期データを返します。");
return new GameData(); // デフォルト
}
try
{
string json = File.ReadAllText(SavePaths.File, Encoding.UTF8);
var data = JsonUtility.FromJson<GameData>(json);
if (data == null) throw new System.Exception("JSONのパースに失敗");
// ここで必要ならマイグレーション
MigrateIfNeeded(data);
return data;
}
catch (System.Exception e)
{
Debug.LogWarning($"ロード失敗: {e.Message}\n初期データにフォールバックします。");
return new GameData();
}
}
// バージョン差分に応じてフィールド補完などを行う
static void MigrateIfNeeded(GameData data)
{
switch (data.version)
{
case 1:
// v1→現行 何もしない
break;
// 例: case 0: data.item ??= "なし"; data.version = 1; break;
}
}
public static void Delete()
{
if (File.Exists(SavePaths.File)) File.Delete(SavePaths.File);
}
}
3. 実際の呼び出し例(MonoBehaviour)
using UnityEngine;
public class GameDataExample : MonoBehaviour
{
void Start()
{
// セーブ例
var save = new GameData
{
level = 10,
score = 1234.5f,
item = "勇者の剣"
};
JsonSaveSystem.Save(save);
// ロード例
GameData loaded = JsonSaveSystem.LoadOrDefault();
Debug.Log($"Loaded → level:{loaded.level}, score:{loaded.score}, item:{loaded.item}");
}
}
4. PlayerPrefs との比較(使い分けの目安)
目的 | PlayerPrefs | JSONファイル |
---|---|---|
少量の設定(音量、難易度など) | ◎ | ○ |
多数の値/ネスト構造 | △(キー管理が煩雑) | ◎ |
デバッグのしやすさ | △ | ◎(テキストで読める) |
破損時の復旧 | ×(キー単位) | ○(バックアップ/アトミック書込可) |
セキュリティ | ×(平文) | △(暗号化を自前で実装すれば○) |
5. よくある落とし穴・ベストプラクティス
- ファイルI/Oはメインスレッドで:小さいJSONならOK。大きい場合は Task.Run 等で非同期化(※Unity 6/.NET 4.x互換で可)。
- 例外ハンドリング:JSON破損や権限エラーに備えて try-catch で初期データにフォールバック。
- バージョン管理:GameData.version を上げ、MigrateIfNeeded に移行処理を追加。
- バックアップ:上書き前に save.json.bak を作る方法も有効。
- 暗号化:チート対策が必要なら、JSONテキストをAES等で暗号化してから保存(ハッシュで改ざん検知も)。
- クラウド同期:プラットフォームのクラウド(iCloud/Google Play Games/Steam Cloud等)にファイルを同期させる設計も可能。
6. 代替:Newtonsoft.Json / System.Text.Json を使う
JsonUtility は 配列/リスト以外の複雑な型や辞書 が苦手です。
柔軟にやるなら Newtonsoft.Json(パッケージ導入)や System.Text.Json(Unity 6 以降で選択肢)を検討できます。
// Newtonsoft.Json 例
using Newtonsoft.Json;
// Install via UPM or asmdefで参照
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
var back = JsonConvert.DeserializeObject<GameData>(json);
辞書やprivate setter、プロパティを扱いたい場合に有効です。
7. UIとの連携(簡易例)
ロードしたデータをUIへ反映・保存ボタンで更新するだけで、設定画面/セーブスロットが作れます。
// 例:Startでロード→UI反映、保存ボタンでSave
public class SettingsScreen : MonoBehaviour
{
GameData _data;
void Start()
{
_data = JsonSaveSystem.LoadOrDefault();
// TODO: UIへ反映(Text、Sliderなど)
}
public void OnClickSave()
{
// TODO: UIから値を反映
JsonSaveSystem.Save(_data);
}
}
8. 仕上げチェックリスト
- persistentDataPath を使っている
- 例外時は初期データにフォールバック
- JSONのバージョンを持たせた
- 書き込みは一時ファイル→置換で安全性UP
- 将来のフィールド追加を想定して実装
まとめ
- PlayerPrefsは“ちょい保存”、JSONは“ちゃんと保存”。
- POCOクラス+JsonUtilityで手早く始め、複雑化してきたら Newtonsoft/System.Text.Json へ。
- アトミック書き込み/マイグレーション/例外処理 を押さえると、実運用に耐えるセーブ機構になる。
次の発展案
- セーブスロット(save1.json, save2.json …)
- 自動バックアップ・復元メニュー
- 暗号化+改ざん検知(HMAC)
- 非同期I/Oでフリーズ防止
- ScriptableObjectと併用した設定プロファイル
訪問数 3 回, 今日の訪問数 3回
ディスカッション
コメント一覧
まだ、コメントがありません