SRPGのセーブ機能は、プレイヤーの利便性を大きく向上させます。
戦闘途中でも保存できれば、安心してプレイできます。
この記事では、実装方法を詳しく解説します。
✨ この記事でわかること
- セーブデータの構造設計
- JSON形式での保存・読み込み
- バイナリ形式での保存・読み込み
- 戦闘途中の状態保存
- 実装例とコード

セーブ機能は、最初から設計しておくことが大切です。後から追加すると、既存のデータ構造を変更する必要があります。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
あなたのオリジナルゲーム、今年こそ完成させませんか?
RPG・アクション・ホラー…Unityで本格ゲームを作りたい人のための学習サイトです。
実際に完成するゲームを題材に、
ソースコード・素材・プロジェクト一式をすべて公開。
仕事や学校の合間の1〜2時間でも、
「写経→改造」で自分のゲームまで作りきれる環境です。
セーブデータの構造設計

セーブデータの構造を最初に決めます。
実装方法を紹介します。
セーブデータクラス
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Collections.Generic; [System.Serializable] public class SaveData { [Header("基本情報")] public int saveSlot; public DateTime saveTime; public string sceneName; [Header("ゲーム状態")] public int currentTurn; public BattleState currentState; public int currentPlayerIndex; public int currentEnemyIndex; [Header("ユニット情報")] public List<UnitSaveData> playerUnits = new List<UnitSaveData>(); public List<UnitSaveData> enemyUnits = new List<UnitSaveData>(); [Header("マップ情報")] public int mapWidth; public int mapHeight; public List<TerrainSaveData> terrainData = new List<TerrainSaveData>(); } [System.Serializable] public class UnitSaveData { public string unitName; public int gridX; public int gridY; public int level; public UnitStats stats; public bool hasMoved; public bool hasActed; } [System.Serializable] public class TerrainSaveData { public int x; public int y; public TerrainType terrainType; } |
このデータ構造で、ゲームの状態を保存できます。
ユニットの位置・HP・状態など、すべての情報を含めます。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
JSON形式での保存・読み込み

JSON形式は、読みやすくデバッグしやすいです。
実装方法を紹介します。
JSON保存の実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
using UnityEngine; using System.IO; public class SaveManager : MonoBehaviour { private const string SAVE_DIRECTORY = "SaveData"; private const string SAVE_FILE_PREFIX = "save_"; public void SaveGame(int slot) { SaveData saveData = CreateSaveData(); saveData.saveSlot = slot; saveData.saveTime = DateTime.Now; string json = JsonUtility.ToJson(saveData, true); string filePath = GetSaveFilePath(slot); // ディレクトリが存在しない場合は作成 string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } File.WriteAllText(filePath, json); Debug.Log($"セーブ完了: {filePath}"); } SaveData CreateSaveData() { SaveData saveData = new SaveData(); // ゲーム状態を取得 TurnManager turnManager = FindObjectOfType<TurnManager>(); saveData.currentTurn = turnManager.currentTurn; saveData.currentState = turnManager.currentState; saveData.currentPlayerIndex = turnManager.currentPlayerIndex; saveData.currentEnemyIndex = turnManager.currentEnemyIndex; // ユニット情報を取得 foreach (var unit in turnManager.playerUnits) { saveData.playerUnits.Add(CreateUnitSaveData(unit)); } foreach (var unit in turnManager.enemyUnits) { saveData.enemyUnits.Add(CreateUnitSaveData(unit)); } // マップ情報を取得 GridMap gridMap = FindObjectOfType<GridMap>(); saveData.mapWidth = gridMap.width; saveData.mapHeight = gridMap.height; return saveData; } UnitSaveData CreateUnitSaveData(Unit unit) { return new UnitSaveData { unitName = unit.name, gridX = unit.gridX, gridY = unit.gridY, level = unit.level, stats = unit.stats, hasMoved = unit.hasMoved, hasActed = unit.hasActed }; } string GetSaveFilePath(int slot) { string fileName = $"{SAVE_FILE_PREFIX}{slot:D2}.json"; return Path.Combine(Application.persistentDataPath, SAVE_DIRECTORY, fileName); } } |
JSON読み込みの実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
public class SaveManager : MonoBehaviour { public bool LoadGame(int slot) { string filePath = GetSaveFilePath(slot); if (!File.Exists(filePath)) { Debug.LogError($"セーブファイルが見つかりません: {filePath}"); return false; } string json = File.ReadAllText(filePath); SaveData saveData = JsonUtility.FromJson<SaveData>(json); // ゲーム状態を復元 RestoreGameState(saveData); Debug.Log($"ロード完了: {filePath}"); return true; } void RestoreGameState(SaveData saveData) { // ターン管理を復元 TurnManager turnManager = FindObjectOfType<TurnManager>(); turnManager.currentTurn = saveData.currentTurn; turnManager.currentState = saveData.currentState; turnManager.currentPlayerIndex = saveData.currentPlayerIndex; turnManager.currentEnemyIndex = saveData.currentEnemyIndex; // ユニットを復元 RestoreUnits(saveData.playerUnits, turnManager.playerUnits); RestoreUnits(saveData.enemyUnits, turnManager.enemyUnits); // マップを復元 RestoreMap(saveData); } void RestoreUnits(List<UnitSaveData> saveDataList, List<Unit> unitList) { for (int i = 0; i < saveDataList.Count && i < unitList.Count; i++) { UnitSaveData saveData = saveDataList[i]; Unit unit = unitList[i]; unit.name = saveData.unitName; unit.SetPosition(saveData.gridX, saveData.gridY, FindObjectOfType<GridMap>()); unit.level = saveData.level; unit.stats = saveData.stats; unit.hasMoved = saveData.hasMoved; unit.hasActed = saveData.hasActed; } } void RestoreMap(SaveData saveData) { GridMap gridMap = FindObjectOfType<GridMap>(); // マップ情報を復元 } } |
このコードで、JSON形式での保存・読み込みが実装できます。
人間が読める形式のため、デバッグしやすいです。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
バイナリ形式での保存・読み込み

バイナリ形式は、ファイルサイズが小さく、読み込みが速いです。
実装方法を紹介します。
バイナリ保存の実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using System.IO; using System.Runtime.Serialization.Formatters.Binary; public class SaveManager : MonoBehaviour { public void SaveGameBinary(int slot) { SaveData saveData = CreateSaveData(); saveData.saveSlot = slot; saveData.saveTime = DateTime.Now; string filePath = GetSaveFilePath(slot, ".dat"); // ディレクトリが存在しない場合は作成 string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } BinaryFormatter formatter = new BinaryFormatter(); using (FileStream stream = new FileStream(filePath, FileMode.Create)) { formatter.Serialize(stream, saveData); } Debug.Log($"セーブ完了: {filePath}"); } public bool LoadGameBinary(int slot) { string filePath = GetSaveFilePath(slot, ".dat"); if (!File.Exists(filePath)) { Debug.LogError($"セーブファイルが見つかりません: {filePath}"); return false; } BinaryFormatter formatter = new BinaryFormatter(); using (FileStream stream = new FileStream(filePath, FileMode.Open)) { SaveData saveData = (SaveData)formatter.Deserialize(stream); RestoreGameState(saveData); } Debug.Log($"ロード完了: {filePath}"); return true; } string GetSaveFilePath(int slot, string extension) { string fileName = $"{SAVE_FILE_PREFIX}{slot:D2}{extension}"; return Path.Combine(Application.persistentDataPath, SAVE_DIRECTORY, fileName); } } |
⚠️ 注意:BinaryFormatterの使用について
- Unity 2021.2以降では、BinaryFormatterが非推奨です
- 代わりに、JSON形式や独自のバイナリ形式を使用することを推奨します
- セキュリティ上の理由から、信頼できないデータの読み込みは避けましょう
バイナリ形式は、ファイルサイズが小さく、読み込みが速いです。
ただし、デバッグが難しいため、開発中はJSON形式がおすすめです。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
戦闘途中の状態保存

戦闘途中の状態を保存するには、すべての情報を含める必要があります。
実装方法を紹介します。
戦闘状態の保存
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
[System.Serializable] public class BattleSaveData { [Header("ターン情報")] public int currentTurn; public BattleState currentState; public int currentPlayerIndex; public int currentEnemyIndex; [Header("選択中のユニット")] public int selectedUnitIndex = -1; public bool isActionSelecting = false; [Header("行動履歴")] public List<ActionHistory> actionHistory = new List<ActionHistory>(); } [System.Serializable] public class ActionHistory { public int turn; public string unitName; public ActionType actionType; public Vector2Int targetPosition; } public class SaveManager : MonoBehaviour { SaveData CreateSaveData() { SaveData saveData = new SaveData(); // 戦闘状態を取得 BattleSystem battleSystem = FindObjectOfType<BattleSystem>(); saveData.battleState = CreateBattleSaveData(battleSystem); // ユニット情報を取得 // ... return saveData; } BattleSaveData CreateBattleSaveData(BattleSystem battleSystem) { BattleSaveData battleData = new BattleSaveData(); battleData.currentTurn = battleSystem.currentTurn; battleData.currentState = battleSystem.currentState; battleData.currentPlayerIndex = battleSystem.currentPlayerIndex; battleData.currentEnemyIndex = battleSystem.currentEnemyIndex; if (battleSystem.selectedUnit != null) { battleData.selectedUnitIndex = battleSystem.playerUnits.IndexOf(battleSystem.selectedUnit); } battleData.isActionSelecting = battleSystem.isActionSelecting; return battleData; } } |
このコードで、戦闘途中の状態を保存できます。
ターン情報、選択中のユニット、行動履歴などを含めます。
状態の復元
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class SaveManager : MonoBehaviour { void RestoreGameState(SaveData saveData) { // マップを復元 RestoreMap(saveData); // ユニットを復元 RestoreUnits(saveData); // 戦闘状態を復元 RestoreBattleState(saveData.battleState); } void RestoreBattleState(BattleSaveData battleData) { BattleSystem battleSystem = FindObjectOfType<BattleSystem>(); battleSystem.currentTurn = battleData.currentTurn; battleSystem.currentState = battleData.currentState; battleSystem.currentPlayerIndex = battleData.currentPlayerIndex; battleSystem.currentEnemyIndex = battleData.currentEnemyIndex; if (battleData.selectedUnitIndex >= 0) { battleSystem.selectedUnit = battleSystem.playerUnits[battleData.selectedUnitIndex]; } battleSystem.isActionSelecting = battleData.isActionSelecting; } } |
保存した状態を復元します。
戦闘を中断した位置から、再開できます。

セーブ機能は、最初から設計しておくことが大切です。後から追加すると、既存のデータ構造を変更する必要があります。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
実装例:完全なセーブシステムと形式の選び方

セーブ機能を実装する際、多くの開発者が「JSONとバイナリ、結局どちらを使うべきか」という壁にぶつかります。

ここでは、それぞれの特性を理解した上での使い分けと、実戦でそのまま使える統合的な実装例を紹介します。
JSON形式とバイナリ形式の使い分け基準
セーブデータの保存形式にはそれぞれメリットとデメリットがあります。
プロジェクトのフェーズや目的に合わせて、最適な方を選択しましょう。
- 開発効率を優先するならJSON
テキスト形式で中身が読めるため、デバッグが非常に容易です。「セーブしたはずのHPが反映されていない」といったトラブル時も、ファイルを直接開いて確認できるため、開発初期から中盤にかけてはJSON形式を強くおすすめします。 - パフォーマンスと秘匿性を優先するならバイナリ
ファイルサイズが小さく、読み込み速度に優れています。また、メモ帳などで簡単に書き換えられないため、リリース後にプレイヤーによるデータの改ざん(チート行為)を少しでも防ぎたい場合に有効です。
結論として、「開発中はJSONでデバッグし、リリース直前に必要に応じてバイナリへ移行する」という流れが、最も効率的でバグを出しにくい賢い選択と言えるでしょう。
実戦向け:完全なセーブシステムの実装
以下に、保存・読み込み・削除・存在確認のすべてを網羅した「完全なセーブシステム」のコードを示します。
今回は汎用性が高く、デバッグもしやすいJSON形式をベースにした実装例です。
スロット管理機能も備えているため、SRPGによくある「複数のセーブデータ」にも即座に対応可能です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
using UnityEngine; using System.IO; using System; public class CompleteSaveSystem : MonoBehaviour { private const string SAVE_DIRECTORY = "SaveData"; private const string SAVE_FILE_PREFIX = "save_"; private const int MAX_SAVE_SLOTS = 10; // 指定したスロットにゲームを保存する public void SaveGame(int slot) { if (slot < 0 || slot >= MAX_SAVE_SLOTS) { Debug.LogError($"無効なスロット番号: {slot}"); return; } SaveData saveData = CreateSaveData(); saveData.saveSlot = slot; saveData.saveTime = DateTime.Now; // JSON形式に変換(第2引数をtrueにすると整形されて読みやすくなります) string json = JsonUtility.ToJson(saveData, true); string filePath = GetSaveFilePath(slot); // 保存先のディレクトリ作成 string directory = Path.GetDirectoryName(filePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } File.WriteAllText(filePath, json); Debug.Log($"セーブ完了: スロット{slot} - パス: {filePath}"); } // 指定したスロットからゲームを読み込む public bool LoadGame(int slot) { if (slot < 0 || slot >= MAX_SAVE_SLOTS) { Debug.LogError($"無効なスロット番号: {slot}"); return false; } string filePath = GetSaveFilePath(slot); if (!File.Exists(filePath)) { Debug.LogError($"セーブファイルが見つかりません: スロット{slot}"); return false; } string json = File.ReadAllText(filePath); SaveData saveData = JsonUtility.FromJson<SaveData>(json); RestoreGameState(saveData); Debug.Log($"ロード完了: スロット{slot}"); return true; } // セーブデータの存在確認(UIの「つづきから」判定などに使用) public bool HasSaveData(int slot) { string filePath = GetSaveFilePath(slot); return File.Exists(filePath); } // セーブ日時を取得(セーブスロットのラベル表示などに便利) public DateTime GetSaveTime(int slot) { string filePath = GetSaveFilePath(slot); if (!File.Exists(filePath)) { return DateTime.MinValue; } string json = File.ReadAllText(filePath); SaveData saveData = JsonUtility.FromJson<SaveData>(json); return saveData.saveTime; } // セーブデータの削除 public void DeleteSave(int slot) { string filePath = GetSaveFilePath(slot); if (File.Exists(filePath)) { File.Delete(filePath); Debug.Log($"セーブ削除: スロット{slot}"); } } // 保存パスの生成 string GetSaveFilePath(int slot) { string fileName = $"{SAVE_FILE_PREFIX}{slot:D2}.json"; return Path.Combine(Application.persistentDataPath, SAVE_DIRECTORY, fileName); } SaveData CreateSaveData() { // ここで現在のゲームマネージャー等からデータを収集する return new SaveData(); } void RestoreGameState(SaveData saveData) { // ここで読み込んだデータを各システムに反映させる } } |
このスクリプトを一つ用意しておくだけで、SRPGにおけるセーブ・ロードの基本機能はほぼ網羅できます。

まずはJSON形式で確実にデータを保存・復元できる仕組みを整え、デバッグの効率を最大化させましょう。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
よくある質問(FAQ)

Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる
あなたのオリジナルゲーム、今年こそ完成させませんか?
RPG・アクション・ホラー…Unityで本格ゲームを作りたい人のための学習サイトです。
実際に完成するゲームを題材に、
ソースコード・素材・プロジェクト一式をすべて公開。
仕事や学校の合間の1〜2時間でも、
「写経→改造」で自分のゲームまで作りきれる環境です。
まとめ

セーブ機能は、最初から設計しておくことが大切です。
JSON形式から始めて、必要に応じてバイナリ形式に変更しましょう。
✅ 今日から始める3ステップ
- ステップ1:セーブデータの構造を定義する(所要1時間)
- ステップ2:JSON形式での保存・読み込みを実装する(所要2時間)
- ステップ3:戦闘状態の保存・復元を実装する(所要3時間)
本格的にUnityを学びたい方は、Unity入門の森で実践的なスキルを身につけましょう。
あなたのペースで、少しずつ進めていけば大丈夫です。
Unity入門の森を見る 初心者歓迎!動画×プロジェクト一式で本格ゲーム制作を学べる



コメント