Unityでのゲーム開発において、変数の値を効率的に管理する方法の一つがDictionaryです。
Dictionaryは、キーに基づいて値にかなり高速にアクセスできるコレクションであり、ゲーム開発の多くの場面で活躍の場があります。
Dictionaryとは?
C#のDictionary<TKey, TValue>は、キーと値のペアをコレクションとして保持するためのジェネリックコレクションです。TKey, TValueはジェネリック型で、キーと格納する値の型を示します。
キーは一意でなければならず、これにより特定の値への高速なアクセスが可能になります。
使い方
宣言と初期化
using System.Collections.Generic;
Dictionary<string, int> myDictionary = new Dictionary<string, int>();
Dictionary<string, int> myDictionary2 = new Dictionary<string, int>(){{"key1", 1}, {"key2", 2}};
Dictionary<string, int> myDictionary3 = new Dictionary<string, int>(100);
Dictionary<string, int> myDictionary4 = new Dictionary<string, int>(myDictionary2);
使用する際はSystem.Collections.Genericをusingに含む必要があります。
Dictionary型の宣言ではサイズを指定せずに宣言することも可能ですが、
myDictionary3のように予めサイズを指定することも可能です。
予め要素数が決まっている場合はサイズを指定した方がパフォーマンス効率がよくなります。
要素の追加(Add, TryAdd)
Add
myDictionary.Add("apple", 3);
“apple”というキーで3の値を追加します。
既にmyDictionaryに”apple”というキーが存在する場合、エラーになります。
TryAdd
myDictionary.TryAdd("apple", 3);
“apple”というキーで3の値を追加し、追加できた場合trueを返します。
既にmyDictionaryに”apple”というキーが存在する場合でもエラーにはならず、falseを返します。
要素の削除(Remove, Clear)
Remove
myDictionary.Remove("apple");
“apple”というキーで要素を削除し、成功したらtrueを返します。
キーが存在しない場合、falseを返します。
Clear
myDictionary.Clear();
全ての要素を削除します。
要素の取得(キー指定, TryGetValue, Keys, Values)
キーの指定
int value = myDictionary["apple"];
“apple”というキーでアクセスして値を取得します。
キーが存在しない場合、エラーになります。
TryGetValue
int value;
myDictionary.TryGetValue("apple", out value);
“apple”というキーでアクセスして値を取得し、取得できた場合trueを返し、値はvalueに格納されます。
キーが存在しない場合でもエラーにはならず、falseを返します。
Keysプロパティ/Valuesプロパティ
foreach (var key in myDictionary.Keys)
{
Console.WriteLine(key); // すべてのキーを表示
}
foreach (var value in myDictionary.Values)
{
Console.WriteLine(value); // すべての値を表示
}
DictionaryのKeysプロパティで全てのキー、Valuesプロパティで全ての値を取得できます。
要素数の取得(Count)
Count
int count = myDictionary.Count;
要素数を返します。
また、Linqメソッドを使うことで下記のように条件を満たしている要素数を数えることも可能です。
using System.Linq;
int count = myDictionary.Count(x => x.Value >= 2); //値が2以上の要素数を数える
int count2 = myDictionary.Count(x => x.Key.Contains("a")); //キーにaという文字が含まれる要素数を数える
要素の存在判定 (ContainsKey, ContainsValue, Any)
ContainsKey
myDictionary.ContainsKey("apple");
指定したキーの要素が存在するかを判定します。
ContainsValue
myDictionary.ContainsValue(1);
指定した値の要素が存在するかを判定します。
Any
myDictionary.Any(x => x.Key.Contains("a")); // キーに"a"が含まれるものが1つでもあるか
myDictionary.Any(x => x.Value >= 2); // 値が2以上のものが1つでもあるか
指定した条件を満たす要素が1つでもあるかどうかを判定します。
UnityでのDictionary
インスペクター
UnityのインスペクターではDictionary型の操作は出来ません。
SerializeFieldなDictionary型をメンバに持たせてオブジェクトにアタッチしても
このようにインスペクター上には何も表示されません。
そのため、インスペクターでDictionaryを扱いたいときは工夫が必要です。
解決方法の一つとして、ジェネリック型のKeyとValueを持つ、Serializableなクラスを定義する方法があります。
[Serializable]
public class MyDictionary<TKey, TValue>
{
[SerializeField] private TKey _key;
[SerializeField] private TValue _value;
public TKey Key => _key;
public TValue Value => _value;
}
public class DictionaryObject : MonoBehaviour
{
[SerializeField] private List<MyDictionary<string, int>> _myDictionaries;
private Dictionary<string, int> _dictionary = new Dictionary<string, int>();
void Awake()
{
foreach (var myDictionary in _myDictionaries)
{
_dictionary.Add(myDictionary.Key, myDictionary.Value);
}
}
}
このようにすることでインスペクター上から疑似的にDictionary型の値を追加したり、削除することが出来ます。
Dictionaryを使う際の注意点
キーの管理が複雑になりがち
Dictionary型では同じキーをAddしようとしたり、存在しないキーに対してアクセスしようとするとエラーになり、Unityでは動作が止まる原因にもなります。
そのため、キーは適切に管理しないといけないですが、ContainsKeyで存在判定をしてからAddしようとすると、それだけで複数行使ってしまうので、プログラムが少し煩雑になっていきます。
敢えてこういう場合はエラーを出したいみたいなケースを除くと、TryAddやTryGetValueを使うのが安全で楽ですね。
本当にDictionaryを使うべきか
Dictionary型はキーでのアクセスが確定している場合、List型に比べてかなり高速で動作します。
ただし、Linqなどを使ってDictionary型のValuesの中から条件を満たすものを探す、などのような使い方をする場合はもともとList型で保持しておいてFindする方が動作が高速です。
どのように要素を検索するか、どのように要素を格納するかをしっかり検討した上でDictionary型は使うようにしましょう。
インスペクターでそのまま使えないこと、キーの管理などを考えると、基本的にはListを使うだけで十分かもしれません。
コメント