【Unity】Inspectorで設定できるDictionaryを作りたい
はじめに
初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!
今回は
UnityのInspectorで設定できるDictionaryの作り方
を紹介します
実際の画面
こちらが、実際の画面です。
Keyにはenum
、Valueにはstring
を設定しています
よくあるInspectorで設定できるDictionary
先に他の記事などでも取り上げられている
Inspector
で設定できるDictionary
の設計を紹介します。
SerializableDictionary
using System;
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// SerializableDictionary
/// </summary>
[Serializable]
public class SerializableDictionary<TKey, TValue, Type> where Type : Pair<TKey, TValue>
{
[SerializeField]
private List<Type> _list = null;
private Dictionary<TKey, TValue> _dictionary = null;
public Dictionary<TKey, TValue> Dictionary
{
get
{
_dictionary ??= ConvertToDictionary();
return _dictionary;
}
}
/// <summary>
/// ConvertToDictionary
/// </summary>
private Dictionary<TKey, TValue> ConvertToDictionary()
{
Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue> ();
foreach(Pair<TKey, TValue> pair in _list)
{
dictionary.Add(pair.key, pair.value);
}
return dictionary;
}
}
/// <summary>
/// Pair
/// </summary>
[Serializable]
public class Pair<TKey, TValue>
{
public TKey key;
public TValue value;
public Pair(TKey key, TValue value)
{
this.key = key;
this.value = value;
}
}
使用例(ExampleSerializableDictionary1)
using System;
using System.Collections.Generic;
using UnityEngine;
public class ExampleSerializableDictionary1 : MonoBehaviour
{
public enum Keys
{
Key1,
Key2,
Key3,
Key4,
Key5,
}
[SerializeField]
private SampleDictionary _sampleDictionary;
private void Start()
{
Debug.Log("------------------------------");
Debug.Log("1回目: インスペクターで設定した状態");
Debug.Log("----------");
foreach (KeyValuePair<Keys, string> pair in _sampleDictionary.Dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
Debug.Log("2回目: Key5を追加");
Debug.Log("----------");
_sampleDictionary.Dictionary.Add(Keys.Key5, "Key5は今追加しました。");
foreach (KeyValuePair<Keys, string> pair in _sampleDictionary.Dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
Debug.Log("3回目: Key3を削除");
Debug.Log("----------");
_sampleDictionary.Dictionary.Remove(Keys.Key3);
foreach (KeyValuePair<Keys, string> pair in _sampleDictionary.Dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
}
}
/// <summary>
/// SampleDictionary
/// </summary>
[Serializable]
public class SampleDictionary : SerializableDictionary<ExampleSerializableDictionary1.Keys, string, SamplePair>
{
}
/// <summary>
/// SamplePair
/// </summary>
[Serializable]
public class SamplePair : Pair<ExampleSerializableDictionary1.Keys, string>
{
public SamplePair(ExampleSerializableDictionary1.Keys key, string value) : base(key, value)
{
}
}
------------------------------
1回目: インスペクターで設定した状態
----------
Key1 -> テスト1
Key2 -> テスト2
Key3 -> テスト3
Key4 -> テスト4
------------------------------
2回目: Key5を追加
----------
Key1 -> テスト1
Key2 -> テスト2
Key3 -> テスト3
Key4 -> テスト4
Key5 -> Key5は今追加しました。
------------------------------
3回目: Key3を削除
----------
Key1 -> テスト1
Key2 -> テスト2
Key4 -> テスト4
Key5 -> Key5は今追加しました。
------------------------------
使用方法
-
SerializableDictionary.cs
で定義したPair
を継承し、任意のKeyとValueの型を指定したクラス(例:SamplePair
)を作り、コンストラクタを作成してbase
に渡す -
SerializableDictionary.cs
で定義したSerializableDictionary
を継承して、任意のKeyとValueの型と1で作成したクラス(例:SamplePair
)を指定したクラス(例:SampleDictionary
)を作る -
2で作ったクラス(例:
SampleDictionary
)を利用したい箇所で[SerializeField]
属性を付けた変数を定義
これで、Inspector
で設定できるDictionary
が作れます。
しかし、面倒・・・
というのも、Inspector
で設定できるDictionary
の数分
SamplePair
やSampleDictionary
のような継承クラスを作る必要があります。
改良版 - Inspectorで設定できるDictionary
なので、今回は面倒な手間を省いた
Inspector
で設定できるDictionary
を作ります
SerializableDictionary
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SerializableDictionary<TKey, TValue> :
Dictionary<TKey, TValue>,
ISerializationCallbackReceiver
{
[Serializable]
public class Pair
{
public TKey key = default;
public TValue value = default;
/// <summary>
/// Pair
/// </summary>
public Pair(TKey key, TValue value)
{
this.key = key;
this.value = value;
}
}
[SerializeField]
private List<Pair> _list = null;
/// <summary>
/// OnAfterDeserialize
/// </summary>
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
Clear();
foreach (Pair pair in _list)
{
if (ContainsKey(pair.key))
{
continue;
}
Add(pair.key, pair.value);
}
}
/// <summary>
/// OnBeforeSerialize
/// </summary>
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
// 処理なし
}
}
使用例(ExampleSerializableDictionary2)
基本的には、通常のDictionary
同じように使えると思います
using System.Collections.Generic;
using UnityEngine;
public class ExampleSerializableDictionary2 : MonoBehaviour
{
public enum Keys
{
Key1,
Key2,
Key3,
Key4,
Key5,
}
[SerializeField]
private SerializableDictionary<Keys, string> _dictionary = null;
/// <summary>
/// Start
/// </summary>
private void Start()
{
Debug.Log("------------------------------");
Debug.Log("1回目: インスペクターで設定した状態");
Debug.Log("----------");
foreach (KeyValuePair<Keys, string> pair in _dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
Debug.Log("2回目: Key5を追加");
Debug.Log("----------");
_dictionary.Add(Keys.Key5, "Key5は今追加しました。");
foreach (KeyValuePair<Keys, string> pair in _dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
Debug.Log("3回目: Key3を削除");
Debug.Log("----------");
_dictionary.Remove(Keys.Key3);
foreach (KeyValuePair<Keys, string> pair in _dictionary)
{
Debug.Log(string.Format("{0} -> {1}", pair.Key, pair.Value));
}
Debug.Log("------------------------------");
}
}
------------------------------
1回目: インスペクターで設定した状態
----------
Key1 -> テスト1
Key2 -> テスト2
Key3 -> テスト3
Key4 -> テスト4
------------------------------
2回目: Key5を追加
----------
Key1 -> テスト1
Key2 -> テスト2
Key3 -> テスト3
Key4 -> テスト4
Key5 -> Key5は今追加しました。
------------------------------
3回目: Key3を削除
----------
Key1 -> テスト1
Key2 -> テスト2
Key4 -> テスト4
Key5 -> Key5は今追加しました。
------------------------------
使用方法
- 利用したい箇所で
[SerializeField]
属性を付けたSerializableDictionary
変数を定義する、このときに合わせて任意のKeyとValueの型を指定する
以上
どうでしょうか?
少しは作りやすくなったのではないでしょうか?
解説
Listの特性
List
はInspector
で設定できる特性を利用しています
例えば、このようなData
というクラスがあったとします。
そのクラスに[Serializable]
属性を付け、Listに[SerializeField]
属性を付けることで
Inspector
上で操作が可能になります。
ちなみに、Data
クラスの変数はpublic
である必要があります
using System;
using System.Collections.Generic;
using UnityEngine;
public class ExampleList1 : MonoBehaviour
{
[Serializable]
public class Data
{
public int key;
public int value;
}
[SerializeField]
private List<Data> _list = null;
}
Listの情報をDictionaryに変換する
ISerializationCallbackReceiver.OnAfterDeserialize
(デシリアライズ後に呼ばれる関数)を利用して、Inspector
で設定された_list
の情報を、そのままDictionary
にAdd
で追加しています。
Dictionary
を継承しているので、そのままDictionary
の関数を使用することができます。
/// <summary>
/// OnAfterDeserialize
/// </summary>
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
Clear(); // DictionaryのClear;
foreach (Pair pair in _list)
{
if (ContainsKey(pair.key))
{
continue;
}
Add(pair.key, pair.value); // DictionaryのAdd();
}
}
さいごに
いかがでしょうか?
今回はSampleDictionary
やSamplePair
ような関数を作らずに
Inspector
で設定できるDictionary
の作り方を紹介しました。
Dictionary
クラスを継承することで、うまい事できたような気がしてます。
もし比較検証してみて、今回紹介した内容が実用的でないなどあれば
(こそっと)教えていただけると嬉しいです!
Discussion