前書き
Unityでゲームの開発をしていると、よく使う便利な型の一つであるDictionary<Key, Value>型があります。キーと値の組み合わせでデータを管理できるため、例えば「アイテム名と所持数」や「武器と強化レベル」など、さまざまな場面で役立ちます。
ですが、ゲームの保存(セーブ/ロード)ではDictionary型は実は扱いにくい型の一つです。
今回は
- 「なぜDictionaryが保存に向いていないのか?」
- 「保存するためにはどうすれば良いのか?」
をUnityでの実装例を交えて解説していきます。
Dictionaryが保存に向いていない理由
結論から言うと、DictionaryはそのままではJSON形式にシリアライズできないからです。
UnityではセーブデータをJSONで保存することが多いですが、ここに落とし穴があります。例えば、以下のようなクラスを保存しようとすると、中身は保存されません。
[System.Serializable]
public class PlayerData
{
public Dictionary<string, int> itemCounts;
}
JsonUtilityではこのDictionaryがまるごと無視されてしまい、セーブデータとして成立しなくなってしまいます。このJsonUtilityではこのDictionaryがまるごと無視されるのはUnityの仕様となります。
【補足】
JsonUtilityは、Unity に組み込まれている JSON 形式のシリアライズ/デシリアライズ用クラスです。C# のオブジェクトを JSON 文字列に変換したり、逆に JSON 文字列を C# のオブジェクトに変換するためのツールです。また以下のリンクにJsonUtilityのリファレンスがありますが、
「You can use this class to generate a JSON representation of an object」
とあり、JsonUtility は JSON の読み書き専用であって、バイナリデータの読み書きには使えないことの記載があります。 Unity - Scripting API: JsonUtility
対策①:List構造に変更する
最もシンプルな方法は、DictionaryをList構造に変換して保存する方法です。以下のように構造を変えれば、UnityのJsonUtilityでも保存可能になります。
[System.Serializable]
public class ItemEntry
{
public string itemName;
public int count;
}
[System.Serializable]
public class PlayerData
{
public List<ItemEntry> itemCounts;
}
保存のときはListに変換、読み込み後にDictionaryに戻すような処理を入れれば、実行時は従来のロジックのまま使えます。
対策②:Json.NETを使う
もう一つの手段は、外部ライブラリ「Newtonsoft.Json(Json.NET)」を使うことです。
今回はJson.NETは使用しませんでしたが、このライブラリを使うことでDictionary型もそのまま保存できるという情報があります。余裕があれば、こちらの方法も検討してみると良いでしょう。
実体験:実際にハマったDictionary保存の落とし穴
TopDown Engineで実装されているSave/Loadシステムを使うと以下のMMSaveLoadGameSoftクラスを使うことになるかなと思います。このとき、SaveLoadMethodで保存形式が選択できますが、大体はJson(Json Encrypted)を使うことになります。
# Binary も選択できますがセキュリティ的に非推奨

以下は、自分が実際に保存処理に組み込んだコードの例です。Dictionary型 の paramCopy を testObject(セーブデータ)に書き込んだところ、ロードしたときには中身が空になっていました。
foreach (var kvp in GameManager.Instance.weaponPoints)
{
string weaponKey = kvp.Key.id.ToString(); // idを使って一意に識別
Dictionary<string, int> paramCopy = new Dictionary<string, int>();
foreach (var param in kvp.Value)
{
paramCopy[param.Key] = param.Value;
}
testObject.SavedWeaponPoints[weaponKey] = paramCopy;
}
今回、Dictionay型が保存に適してないと知らなかったため、処理を変えるまでに1か月ぐらい時間がかかってしましました。ので、今回は以下のようにそもそもDictionay型をやめてList型に変更してみました。
以前は以下のようにDictionay型で宣言してた武器パラメータですが、
weaponPoints[weapon] = new Dictionary<string, int>
{
{ "Attack", 0 },
{ "Speed", 0 },
{ "Ammo", 0 },
{ "Special", 0 }
};
以下の①のようにメンバーで各種パラメータを定義しました。その状態で②③を実行することでうまくDictionay型で宣言していた武器パラメータをList型で保存することが出来るようになりました。
①:Listで定義するメンバーを宣言する(セーブ用のデータ構造)
public class WeaponPointData
{
public int weaponId;
public int attack;
public int speed;
public int ammo;
public int special;
}
②:リストとしてセーブデータを保持する
public List<WeaponPointData> weaponPointsList = new List<WeaponPointData>();
③:セーブ時にデータをコピーする
testObject.weaponPointsList = GameManager.Instance.weaponPointsList;
TopDown EngineのSave/Loadサンプルシーンについて
TopDown EngineのSave/Loadサンプルシーンについてもまとめた記事がありますので、Save/Loadの仕組みについて気になる方はこちらの記事もご確認ください。
まとめ
Unityではとても便利なDictionaryですが、保存の観点から見ると少しクセがあります。
セーブ/ロードを実装する際は、シリアライズ可能な構造に変換することを意識しましょう。
方法 | メリット | デメリット |
---|---|---|
Listに変換して保存 | Unity標準で動作する | コードがやや煩雑 |
Json.NETを使う | 複雑な構造でも保存できる | 外部DLLが必要 |
この記事が、Unityでセーブ処理を実装している方の助けになれば幸いです!
コメント