【Unity】【エディタ拡張】配列やリストのInspector表示を改良する

Unityのデフォルトのリスト表示は場合によっては結構見づらいので拡張してみました。

Unity2018.3.1

作るもの

リストをこんな感じで表示するためのクラスを作ります。

f:id:halya_11:20190203203005g:plain

インタフェースはReorderableListと同じような感じにできたらいいかなーと思います。
全部のリストに適用するのではなく必要な時だけ使える&ある程度拡張できる感じ。
あとEditorGUIEditorGUILayoutの両方から使えることも重要ですね。

anchan828.github.io

ソースコード

早速ですがソースコードです。

using UnityEngine;
using UnityEditor;

public class ExampleCustomList
{
    /// <summary>
    /// 高さ
    /// </summary>
    public float Height { get { return _property.isExpanded ? (ArraySize + 1) * LineHeight : LineHeight; } }

    private float ContentHeight { get; } = EditorGUIUtility.singleLineHeight;
    private float LineHeight { get; } = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing + 4;
    private int ArraySize { get { return _property.arraySize; } }
        
    /// <summary>
    /// ヘッダが描画されたときのコールバック
    /// </summary>
    public event System.Action<Rect> drawHeaderCallback;
    /// <summary>
    /// 要素が描画されたときのコールバック
    /// </summary>
    public event System.Action<Rect, int> drawElementCallback;

    private SerializedProperty _property;
        
    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="property">配列のプロパティ</param>
    public ExampleCustomList(SerializedProperty property)
    {
        _property = property;
            
        // デフォルトのヘッダ描画処理を登録
        drawHeaderCallback = rect => {
            rect.xMin += 10;
            EditorGUI.PropertyField(rect, property);
        };
        // デフォルトの要素描画処理を登録
        drawElementCallback = (rect, index) => {
            EditorGUI.PropertyField(rect, _property.GetArrayElementAtIndex(index));
                
        };
    }

    /// <summary>
    /// EditorGUILayout環境で描画する
    /// </summary>
    public void DoLayoutList()
    {
        var lastRect = GUILayoutUtility.GetRect(new GUIContent(""), GUIStyle.none);
        var rect = GUILayoutUtility.GetRect(lastRect.width, Height);
        DoList(rect);
    }

    /// <summary>
    /// EditorGUI環境で描画する
    /// </summary>
    public void DoList(Rect position)
    {
        // 色の定義
        var backgroundDefaultColor = GUI.backgroundColor;
        var backgroundLightColor = backgroundDefaultColor;
        var backgroundDarkColor = backgroundLightColor * 0.8f;
        backgroundDarkColor.a = 1;

        var isExpanded = _property.isExpanded;

        // 外枠を描画
        var outlineRect = position;
        position.height = Height;
        GUI.Box(outlineRect, "");
            
        var fieldRect = position;
        fieldRect.height = LineHeight;

        // ヘッダを描画
        var headerRect = fieldRect;
        var plusButtonRect = fieldRect;
        plusButtonRect.xMin = fieldRect.xMax - fieldRect.height;
        headerRect.xMax -= plusButtonRect.width - 1;
        GUI.Box(headerRect, "");
        drawHeaderCallback(GetContentRect(headerRect));
        if (GUI.Button(plusButtonRect, "+", GUI.skin.box)) {
            _property.arraySize ++;
        }

        if (isExpanded) {
            for (int i = 0; i < ArraySize; i++) {
                fieldRect.y += LineHeight;
                    
                // 背景を描画
                var backgroundRect = fieldRect;
                backgroundRect.xMin += 1;
                backgroundRect.xMax -= 1;
                if (ArraySize == i + 1) {
                    backgroundRect.yMax -= 1;
                }
                EditorGUI.DrawRect(backgroundRect, i % 2 == 0 ? backgroundDarkColor : backgroundLightColor);
                // プロパティを描画
                var propertyRect = GetContentRect(fieldRect);
                drawElementCallback(propertyRect, i);

                // マイナスボタンを描画
                var minusButtonRect = fieldRect;
                minusButtonRect.height -= 4;
                minusButtonRect.y += 2;
                minusButtonRect.xMin = minusButtonRect.xMax - minusButtonRect.height - 2;
                minusButtonRect.xMax -= 2;
                if (GUI.Button(minusButtonRect, "X")) {
                    _property.DeleteArrayElementAtIndex(i);
                    break;
                }
            }
        }
    }

    private Rect GetContentRect(Rect fieldRect)
    {
        fieldRect.height = ContentHeight;
        fieldRect.y += (LineHeight - ContentHeight) / 2;
        fieldRect.xMin += 4;
        fieldRect.xMax -= 24;
        return fieldRect;
    }
}

ざっと作ったので細かいところは使いながら調整といった感じです。

使い方

使い方はこんな感じです。
EditorGUILayoutを使う環境で使った例です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class Example : MonoBehaviour
{
    [SerializeField]
    private List<int> _exampleList;

}

[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
    private ExampleCustomList _list;
    
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        var listProp = serializedObject.FindProperty("_exampleList");
        if (_list == null) {
            _list = new ExampleCustomList(listProp);
        }
        _list.DoLayoutList();

        serializedObject.ApplyModifiedProperties();
    }
}

結果

結果はこんな感じになりました。

f:id:halya_11:20190203203005g:plain