自作の構造体をインスペクター上に表示するかどうかを制御

2023/06/05 : 初稿
Unity : 2021.3.15f1

# やりたいこと

自作のクラスや構造体をMonoBehaviorのシリアライズパラメータとして持たせるが、
何かフラグなどをみてその内容を表示したりしなかったりしたい。

カスタムエディターを作ればいいけど面倒。


# 実現方法

カスタムアトリビュートを作成。
表示するかしないかの判定は
MonoBehaviourにinterfaceを継承させてそのメソッドで。

```cs:ToggleShowInspector.cs
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Utils
{
    public sealed class ToggleShowInspectorAttribute : PropertyAttribute
    {
        public interface IHandler
        {
            bool ShouldShowInInspector(UnityEngine.Object obj, string propName);
        }
    }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(ToggleShowInspectorAttribute))]
    public sealed class ToggleShowInspectorAttributeDrawer : PropertyDrawer
    {
        // OnGUI
        public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
        {
            // 所有するMonoBehavior取得
            var owner = _property.serializedObject.targetObject;

            // そいつがIHandlerを継承しているならそれを取得
            var handler = owner as ToggleShowInspectorAttribute.IHandler;

            // そいつに表示すべきかどうかを確認
            var shouldShow = (handler != null && handler.ShouldShowInInspector(owner, _property.name));

            // 表示しない
            if (!shouldShow) {
                return;
            }

            // 表示する
            if (_property.hasVisibleChildren) {
                DrawDefaultGUI(_position, _property, _label);
            } else {
                EditorGUI.PropertyField(_position, _property, _label);
            }
        }

        // GetPropertyHeight
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return GetDefaultPropertyHeight(property, label);
        }

        // プロパティの描画
        static void DrawDefaultGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            // プロパティ取得
            property = property.serializedObject.FindProperty(property.propertyPath);

            // 表示エリア
            var fieldRect = position;
            fieldRect.height = EditorGUIUtility.singleLineHeight;
            using (new EditorGUI.PropertyScope(fieldRect, label, property)) {
                // ラベル
                if (property.hasVisibleChildren) { // 子要素があれば折り畳み表示
                    property.isExpanded = EditorGUI.Foldout(fieldRect, property.isExpanded, label);
                } else { // 子要素が無ければラベルだけ表示
                    EditorGUI.PropertyField(fieldRect, property);
                    return;
                }
                fieldRect.y += EditorGUIUtility.singleLineHeight;
                fieldRect.y += EditorGUIUtility.standardVerticalSpacing;

                // 展開しているなら中身
                if (property.isExpanded) {
                    using (new EditorGUI.IndentLevelScope()) {
                        // 最初の要素を描画
                        if (!property.NextVisible(true)) {
                            EditorGUI.PropertyField(fieldRect, property, true);
                        } else {
                            var depth = property.depth;
                            EditorGUI.PropertyField(fieldRect, property, true);
                            fieldRect.y += EditorGUI.GetPropertyHeight(property, true);
                            fieldRect.y += EditorGUIUtility.standardVerticalSpacing;

                            // それ以降の要素を描画
                            while (property.NextVisible(false)) {
                                // depthが最初の要素と同じもののみ処理
                                if (property.depth != depth) {
                                    break;
                                }

                                // 要素描画
                                EditorGUI.PropertyField(fieldRect, property, true);
                                fieldRect.y += EditorGUI.GetPropertyHeight(property, true);
                                fieldRect.y += EditorGUIUtility.standardVerticalSpacing;
                            }
                        }
                    }
                }
            }
        }

        // プロパティの高さを取得
        static float GetDefaultPropertyHeight(SerializedProperty property, GUIContent label)
        {
            var height = 0.0f;

            // プロパティ取得
            property = property.serializedObject.FindProperty(property.propertyPath);

            // ラベルの高さ
            height += EditorGUIUtility.singleLineHeight;
            height += EditorGUIUtility.standardVerticalSpacing;

            // 子要素が無い/畳んでいるならラベルだけ
            if (!property.hasVisibleChildren || !property.isExpanded) {
                return height;
            }

            // 最初の要素
            if (property.NextVisible(true)) {
                var depth = property.depth;
                height += EditorGUI.GetPropertyHeight(property, true);
                height += EditorGUIUtility.standardVerticalSpacing;

                // それ以降の要素
                while (property.NextVisible(false)) {
                    // depthが最初の要素と同じもののみ処理
                    if (property.depth != depth) {
                        break;
                    }
                    height += EditorGUI.GetPropertyHeight(property, true);
                    height += EditorGUIUtility.standardVerticalSpacing;
                }

                // 最後はスペース不要なので削除
                height -= EditorGUIUtility.standardVerticalSpacing;
            }

            // 高さを返す
            return height;
        }
    }
#endif
}
```

使い方はこんな感じ。
```Test.cs
using UnityEngine;
using System;
using Utils;

public class Test : MonoBehaviour, ToggleShowInspectorAttribute.IHandler
{
    // トグル
    [SerializeField] bool Toggle;

    // なんかデータ
    [Serializable]
    struct SomethingData
    {
        public string StrData;
    }
    [SerializeField][ToggleShowInspector] SomethingData Data;

    // ToggleShowInspectorAttribute.IHandler
    public bool ShouldShowInInspector(UnityEngine.Object instance, string propName)
    {
        // get instance
        var main = instance as Test;
        if (main == null) {
            return false;
        }

        // Toggleがtrueの時だけDataを表示
        if (propName == "Data") {
            return main.Toggle;
        }

        // 表示しない
        return false;
    }
}
```