【Unity】UnityでDictionary型はゲームの保存に適していない?その理由と対策方法を解説

スポンサーリンク
Unity
スポンサーリンク

前書き

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でセーブ処理を実装している方の助けになれば幸いです!

コメント

タイトルとURLをコピーしました