【Unity】【エディタ拡張】EditorGUI環境でMonoBehaviourのインスペクタを描画する

PropertyDrawerなどGUILayout系が使えない環境でMonoBehaviourのインスペクタを描画する方法です。
インスペクタはGUILayout環境を前提としているので結構hackyなことをしています。意外と大変でした。

Unity2018.3.6

ソースコード

ソースコードは次の通りです。

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

/// <summary>
/// EditorGUI環境で特定のMonoBehaviourのInspectorのGUIを描画するためのクラス
/// </summary>
public class InspectorGUIDrawer
{
    private class PropertyData
    {
        public SerializedObject so;
        public Dictionary<string, SerializedProperty> properties = new Dictionary<string, SerializedProperty>();
    }
    
    private Dictionary<MonoBehaviour, PropertyData> _propertyDataPerObject = new Dictionary<MonoBehaviour, PropertyData>();
    private PropertyData _property;

    private void Init(MonoBehaviour obj)
    {
        if (_propertyDataPerObject.TryGetValue(obj, out _property)){
                return;
        }
        
        _property = new PropertyData();
            
        var so = new SerializedObject(obj);
        so.Update();
        _property.so = so;
        
        var iter = so.GetIterator();
        iter.NextVisible(true);
        
        while(iter.NextVisible(false))
        {
            var property = so.FindProperty(iter.propertyPath);
            _property.properties.Add(iter.propertyPath, property);
        }
        _propertyDataPerObject.Add(obj, _property);
    }
                
    /// <summary>
    /// Inspectorを描画する
    /// </summary>
    public void DrawInspectorGUI(Rect rect, MonoBehaviour obj)
    {
        if (obj == null) {
            return;
        }

        Init(obj);

        var so = _property.so;
        so.Update();
        
        var iter = so.GetIterator();
        iter.NextVisible(true);
        
        while(iter.NextVisible(false))
        {
            var prop = _property.properties[iter.propertyPath];
            EditorGUI.PropertyField(rect, prop, true);
            rect.y += EditorGUI.GetPropertyHeight(prop, true);
            rect.y += EditorGUIUtility.standardVerticalSpacing;
        }

        so.ApplyModifiedProperties();
    }
    
    /// <summary>
    /// Inspectorの高さを取得する
    /// </summary>
    public float GetInspectorGUIHeight(MonoBehaviour obj)
    {
        if (obj == null) {
            return 0.0f;
        }

        Init(obj);

        var so = _property.so;
        var height = 0.0f;
        
        var iter = so.GetIterator();
        iter.NextVisible(true);

        while(iter.NextVisible(false))
        {
            var prop = _property.properties[iter.propertyPath];
            height += EditorGUI.GetPropertyHeight(prop, true);
            height += EditorGUIUtility.standardVerticalSpacing;
        }

        return height;
    }
}

使い方

このクラスをこんな感じで使います。

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

[System.Serializable]
public class Example
{
    [SerializeField]
    private ExampleBehaviour _exampleBehaviour;
}

[CustomPropertyDrawer(typeof(Example))]
public class ExampleDrawer : PropertyDrawer
{
    private class PropertyData
    {
        public SerializedProperty exampleBehaviourProperty;
    }
    
    private Dictionary<string, PropertyData> _propertyDataPerPropertyPath = new Dictionary<string, PropertyData>();
    private PropertyData _property;
    private InspectorGUIDrawer _inspectorGUIDrawer = new InspectorGUIDrawer();

    private void Init(SerializedProperty property)
    {
        if (_propertyDataPerPropertyPath.TryGetValue(property.propertyPath, out _property)){
                return;
        }
        
        _property = new PropertyData();
        _property.exampleBehaviourProperty = property.FindPropertyRelative("_exampleBehaviour");
        _propertyDataPerPropertyPath.Add(property.propertyPath, _property);
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        Init(property);
        var fieldRect = position;
        fieldRect.height = EditorGUIUtility.singleLineHeight;

        using (new EditorGUI.PropertyScope(fieldRect, label, property)) 
        {
            // MonoBehaviour以外のプロパティを描画
            EditorGUI.PropertyField(fieldRect, _property.exampleBehaviourProperty);
            fieldRect.y += EditorGUIUtility.singleLineHeight;
            fieldRect.y += EditorGUIUtility.standardVerticalSpacing;
        }
        
        // MonoBehaviourのインスペクタを描画
        _inspectorGUIDrawer.DrawInspectorGUI(fieldRect, _property.exampleBehaviourProperty.objectReferenceValue as MonoBehaviour);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        Init(property);

        float height = 0.0f;
        // プロパティの高さ
        height += EditorGUIUtility.singleLineHeight;
        height += EditorGUIUtility.standardVerticalSpacing;

        // インスペクタの高さ
        height += _inspectorGUIDrawer.GetInspectorGUIHeight(_property.exampleBehaviourProperty.objectReferenceValue as MonoBehaviour);

        return height;
    }
}

結果

f:id:halya_11:20190220160252p:plain