【Unity】エディタ拡張でSortingLayerのフィールドを表示する


このメニューを出したい


標準のエディタ拡張APIでは、Sorting Layerを編集するポップアップを出す手段がありません。
これを手動で出せるようにするというお話です。

方針

実はSortingLayerのリストを表示するAPIがEditorGUI.SortingLayerField()として存在しているのですが、internalに指定されているために直接使用することができなくなっています。
当初はそのままコピペして動かそうと思ったのですが、結局別のinternalなメンバーにアクセスする必要が出てきてしまったので、EditorGUI.SortingLayerField()を直接リフレクションで呼び出すことにしました。

Unityでinternalなメンバにアクセスするにはasmrefを使う方法なんかもあるのですが、今回は相手がUnityEditor.dllなので使えず。素直にリフレクションを使います。

実装

エディタ描画の度にリフレクションさせるとエディタのパフォーマンスに影響しそうな気がするので、初回だけデリゲートを動的生成して2回目以降はキャッシュを使います。

SortingLayerEditorUtility.cs
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace Ruccho.Utilities
{
    public static class SortingLayerEditorUtility
    {

        private static GUIStyle boldPopupStyle;
        private static GUIStyle BoldPopupStyle
        {
            get
            {
                if (boldPopupStyle == null)
                {
                    boldPopupStyle = new GUIStyle(EditorStyles.popup);
                    boldPopupStyle.fontStyle = FontStyle.Bold;
                }
                return boldPopupStyle;
            }
        }

        private delegate void SortingLayerFieldDelegate(Rect position, GUIContent label, SerializedProperty layerID,
            GUIStyle style, GUIStyle labelStyle);

        private static SortingLayerFieldDelegate sortingLayerFieldDelegate = default;

        private static bool HasPrefabOverride(SerializedProperty property)
        {
            return property != null && property.serializedObject.targetObjects.Length == 1 && property.isInstantiatedPrefab && property.prefabOverride;
        }

        public static void SortingLayerFieldLayout(GUIContent label, SerializedProperty layerID)
        {
            var hasPrefabOverride = HasPrefabOverride(layerID);
            var style = hasPrefabOverride ? BoldPopupStyle : EditorStyles.popup;
            var labelStyle = hasPrefabOverride ? EditorStyles.boldLabel : EditorStyles.label;
            SortingLayerFieldLayout(label, layerID, style, labelStyle);
        }

        public static void SortingLayerFieldLayout(GUIContent label, SerializedProperty layerID, GUIStyle style, GUIStyle labelStyle)
        {
            Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, style);
            SortingLayerField(rect, label, layerID, style, labelStyle);
        }

        public static void SortingLayerField(Rect position, GUIContent label, SerializedProperty layerID)
        {
            var hasPrefabOverride = HasPrefabOverride(layerID);
            var style = hasPrefabOverride ? BoldPopupStyle : EditorStyles.popup;
            var labelStyle = hasPrefabOverride ? EditorStyles.boldLabel : EditorStyles.label;
            SortingLayerField(position, label, layerID, style, labelStyle);
        }

        public static void SortingLayerField(Rect position, GUIContent label, SerializedProperty layerID,
            GUIStyle style, GUIStyle labelStyle)
        {
            if (sortingLayerFieldDelegate == default)
            {
                var editorGuiType = typeof(EditorGUI);
                var sortingLayerFieldMethod =
                    editorGuiType.GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic);

                if (sortingLayerFieldMethod == null) return;

                sortingLayerFieldDelegate = (SortingLayerFieldDelegate)
                    Delegate.CreateDelegate(typeof(SortingLayerFieldDelegate), sortingLayerFieldMethod);
            }

            sortingLayerFieldDelegate?.Invoke(position, label, layerID, style, labelStyle);
        }
    }
}

ここまでやればSortingLayerEditorUtility.SortingLayerFieldLayout() または SortingLayerEditorUtility.SortingLayerField()で呼び出しが可能です。

参考

neue cc - C#での動的なメソッド選択における定形高速化パターン