うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】UnityEditor の List の表示を変更する

Unity で IList (Array や List) を利用すると下図のような Editor UIが表示される

f:id:hacchi_man:20200131005134p:plain

これだと途中の要素を消すことができず、配列のサイズを変えたいときにも
数値を入力するのが面倒なので拡張することにした

実装

Inspector で利用することを想定して SerializedProperty を利用した拡張を作成する

f:id:hacchi_man:20200131005514p:plain

作成したメソッドを利用して描画されたのが上記画像になる

「+」ボタンで要素を一つづつ追加でき
特定の要素を「ー」で削除することができる

丸枠の「+」と「ー」は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();
    }
}