2017年12月6日

Unity 意外と楽しいエディター拡張 ~すっきり配列編~


エディター拡張で試したことをいろいろメモ、その3。今回は配列の要素をひとつずつ表示してみます…


※このページの内容の動作確認にはUnity5.6を使用しています。
配列やリストに大量のデータを入れて管理する、という状況があったとしましょう。それも、たとえば「マップに配置するアイテムの情報(各ゲームオブジェクトへの参照含む)」なんかを1000個とか2000個とかの単位で格納しちゃうとします。

  1. using UnityEngine;
  2.  
  3. public class MapGenerator : MonoBehaviour {
  4.  
  5. public ItemData[] items;
  6. [System.Serializable]
  7. public class ItemData
  8. {
  9.   public string name;
  10.   public GameObject obj;
  11.   public Vector3 pos;
  12. }
  13. }

で、あろうことかそれをインスペクタ上で確認したいなーなんて思っちゃったとしたら… 一体どうなるでしょう。Element 0、Element 1、Element 2… とずらーっと表示されたElementの中から目当てのものを探してクリック、展開された要素の値を修正…。想像しただけで地獄ですが、実際やってみると「見にくい」とか「効率が悪い」とかの前に動作が重すぎてUnityが固まりかけます。あぶねぇ…

親の仇のように大量に表示されるElement 重くてモッサリする

それを解決する案のひとつとして、カスタムエディタを利用して配列やリストの要素のうち選択したひとつだけを表示することで対処してみましょう。かなり単純ではありますが一応こんな感じでつくってみました。

  1. using UnityEditor;
  2.  
  3. [CustomEditor(typeof(MapGenerator))]
  4. public class MapGeneratorEditor : Editor {
  5.  
  6. SerializedProperty itemsProp;
  7. int currentIndex;
  8.  
  9. private void OnEnable()
  10. {
  11.   itemsProp = serializedObject.FindProperty("items");
  12. }
  13.  
  14. public override void OnInspectorGUI()
  15. {
  16.   serializedObject.Update();
  17.   
  18.   if(itemsProp != null && itemsProp.arraySize != 0)
  19.   {
  20.    // 何番目の要素かを選択するスライダーを表示
  21.    currentIndex = EditorGUILayout.IntSlider(currentIndex, 0, itemsProp.arraySize - 1);
  22.  
  23.    // 選択した要素を表示
  24.    SerializedProperty selectedItemProp = itemsProp.GetArrayElementAtIndex(currentIndex);
  25.    EditorGUILayout.PropertyField(selectedItemProp, true);
  26.    selectedItemProp.isExpanded = true;
  27.   }
  28.   
  29.   serializedObject.ApplyModifiedProperties();
  30. }
  31. }

スライダーで選択した要素がひとつずつ表示される


基本的にSerializedObjectを通して値を変更する、ということは前回説明したとおりです。たとえばfloatの値にアクセスするときはfloatValueを使用するのでした。同じように、配列の要素数を取得したいときはarraySizeで取得できます。さらに配列の要素をプロパティとして参照したいときはGetArrayElementAtIndex(n)を使います。

  1. EditorGUILayout.PropertyField(selectedItemProp, true);

25行目、PropertyFieldの二つ目の引数にtrueを指定していることに注意してください。これは"includeChildren"パラメータで、つまり子要素(折りたたまれている要素)までインスペクタに表示するかどうかを設定する項目です。自作のクラスを配列やリストにした場合、PropertyFieldを使うときは通常これをtrueにしないと各要素が表示されません。

ちなみに、配列・リストの要素のさらにその中のフィールドにアクセスしたい場合はFindPropertyRelativeを使います。なんのこっちゃわからないと思うので次の例を見てください。

// 選択中の要素の name を取得しログに出力する
SerializedProperty nameProp = selectedItemProp.FindPropertyRelative("name");
string itemName = nameProp.stringValue;
Debug.Log(itemName);

選択中の要素にnameとobjとposという3つのメンバ変数があるわけですが、それをFindPropertyRelativeを使って探し取得しているわけです。プロパティのもつプロパティを探す時は"Relative"を使用すると覚えておきましょう。

  1. selectedItemProp.isExpanded = true;

26行目のisExpandedというのはその要素を展開した状態にして表示するかということです。そのままだと折りたたまれた状態をいちいちクリックしないといけないので最初から強制的に展開させています。

てなわけで、配列の要素をひとつずつ表示させることができました。

欠点を挙げるなら、配列・リストのリサイズがめんどくさいことでしょうか。デフォルトであれば"Size"を自由に指定できますが、上のやり方だと"Size"の表示も消えてしまいます。要素数は元のスクリプトで管理するなり、サイズ変更用のメソッドを実装するなりしたほうがいいかもしれません。

PropertyDrawerを使うなど配列の表示形式を変更する方法は他にもいろいろあると思いますが、今回はカスタムエディタでささっと実現してみました。

0 件のコメント:

コメントを投稿