Unity で IList (Array や List) を利用すると下図のような Editor UIが表示される
これだと途中の要素を消すことができず、配列のサイズを変えたいときにも
数値を入力するのが面倒なので拡張することにした
実装
Inspector で利用することを想定して SerializedProperty
を利用した拡張を作成する
作成したメソッドを利用して描画されたのが上記画像になる
「+」ボタンで要素を一つづつ追加でき
特定の要素を「ー」で削除することができる
丸枠の「+」と「ー」はExpandAllと CollapseAll を実行できる
IList の中に IListがあった場合でも正しく表示されるように対応してある
コード
using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; public static class CustomEditorUtility { public static void DrawList(SerializedProperty self) { if (!self.isArray || self.propertyType == SerializedPropertyType.String) { EditorGUILayout.PropertyField(self, new GUIContent(self.displayName), true); return; } using (new GUILayout.HorizontalScope()) { EditorGUILayout.PropertyField(self, new GUIContent(string.Format("{0} [{1}]", self.displayName, self.arraySize)), false); GUILayout.FlexibleSpace(); if (GUILayout.Button(EditorGUIUtility.TrIconContent("d_winbtn_graph_max_h"), "RL FooterButton", GUILayout.Width(16))) { self.isExpanded = true; for (var i = 0; i < self.arraySize; i++) self.GetArrayElementAtIndex(i).isExpanded = true; return; } if (GUILayout.Button(EditorGUIUtility.TrIconContent("d_winbtn_graph_min_h"), "RL FooterButton", GUILayout.Width(16))) { self.isExpanded = false; for (var i = 0; i < self.arraySize; i++) self.GetArrayElementAtIndex(i).isExpanded = false; return; } if (GUILayout.Button(EditorGUIUtility.TrIconContent("Toolbar Plus"), "RL FooterButton", GUILayout.Width(16))) self.InsertArrayElementAtIndex(self.arraySize); } if (!self.isExpanded) return; using (new EditorGUI.IndentLevelScope(1)) { if (self.arraySize <= 0) EditorGUILayout.LabelField("Array is Empty"); for (var i = 0; i < self.arraySize; i++) { var prop = self.GetArrayElementAtIndex(i); using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PropertyField(prop, new GUIContent(i.ToString()), prop.propertyType != SerializedPropertyType.Generic); if (GUILayout.Button(EditorGUIUtility.TrIconContent("Toolbar Minus"), "RL FooterButton", GUILayout.Width(16))) { self.DeleteArrayElementAtIndex(i); return; } } if (prop.propertyType != SerializedPropertyType.Generic || !prop.isExpanded) continue; using (new EditorGUI.IndentLevelScope(1)) { using (new GUILayout.VerticalScope("box")) { var skipCount = 0; while (prop.NextVisible(true)) { if (skipCount > 0) { skipCount--; continue; } if (prop.depth != self.depth + 2) break; if (prop.isArray && prop.propertyType != SerializedPropertyType.String) { DrawList(prop); skipCount = prop.arraySize + 1; continue; } EditorGUILayout.PropertyField(prop, false); } } } } } } }
検証に利用したサンプルクラス
using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; public class SampleMonoBehaviour : MonoBehaviour { [SerializeField] private List<string> list1; [SerializeField] private List<Sample> list2; [Serializable] private class Sample { public List<int> _int; public List<string> _string; public List<Sample2> _bool; } [Serializable] private class Sample2 { public int IntValue; public string StringValue; } } [CustomEditor(typeof(SampleMonoBehaviour))] public class SampleMonoBehaviourEditor : Editor { private SerializedProperty _Property1; private SerializedProperty _Property2; private void Awake() { _Property1 = serializedObject.FindProperty("list1"); _Property2 = serializedObject.FindProperty("list2"); } public override void OnInspectorGUI() { serializedObject.Update(); CustomEditorUtility.DrawList(_Property1); CustomEditorUtility.DrawList(_Property2); serializedObject.ApplyModifiedProperties(); } }