久々にエディタ拡張で盛大にハマったので自分用にメモしておきます。
Custom AttributeとCustom Property Drawerを組み合わせたらInspectorがうまく表示されなくなった件とその対応方法です。
Unity2018.3.1
問題点
いま、SomeClass
というSerializableなクラスと、そのインスタンスをシリアライズしたExample
クラスがあるとします。
public class Example : MonoBehaviour { public SomeClass _example; } [System.Serializable] public struct SomeClass { [SerializeField] private int _someField; }
Inspectorはこんな感じに表示されます。まあ普通です。
次にSomeClass
のCustomPropertyDrawer
を定義してInspectorの見え方を変えたとします。
using UnityEngine; using UnityEditor; [CustomPropertyDrawer(typeof(SomeClass))] public class SomeClassDrawer : PropertyDrawer { private float LineHeight { get { return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; } } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var someProp = property.FindPropertyRelative("_someField"); var fieldRect = position; fieldRect.height = LineHeight; using (new EditorGUI.PropertyScope(fieldRect, label, property)) { var preIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; // プロパティを描画 EditorGUI.PropertyField(fieldRect, someProp, new GUIContent("拡張テスト")); EditorGUI.indentLevel = preIndent; } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return LineHeight; } }
Inspectorは次のように変わります。フィールド名を日本語にしただけです。
さらにここで、PropertyAttribute
を一つ作ります。
using UnityEngine; using UnityEditor; [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = true)] public class ExampleAttribute : PropertyAttribute { } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(ExampleAttribute))] public class ExampleAttributeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.PropertyField(position, property, label, true); } public override float GetPropertyHeight (SerializedProperty property, GUIContent label) { return EditorGUI.GetPropertyHeight(property); } } #endif
そしてこのアトリビュートを最初のSomeClass
のインスタンスに指定します。
public class Example : MonoBehaviour { [Example] public SomeClass _example; }
すると、せっかく拡張したInspectorの表示が元に戻ってしまいます。
まあ一つのフィールドに対してPropertyDrawer
を二つ使ってしまっているのでそれはそうかなという感じです。
アトリビュートによるPropertyDrawerのほうが優先されてしまった感じです。
ただこれでは、上記のようにしてインスペクタを拡張したフィールドに
そのフィールドの可視状態を指定するアトリビュートを設定するときなどに困ってしまいます。
なので解決してみます。
解決方法
色々試した結果、スマートには解決できなかったのですが解決方法を紹介します。
まず、CustomPropertyDrawer
の対象クラスとDrawerのインスタンスとの紐づけを行うstaticなクラスを定義します。
using UnityEditor; using System.Collections.Generic; public static class PropertyDrawerDatabase { private static Dictionary<System.Type, PropertyDrawer> _drawers = new Dictionary<System.Type, PropertyDrawer>(); static PropertyDrawerDatabase() { _drawers = new Dictionary<System.Type, PropertyDrawer>(); // クラスと対応するPropertyDrawerを登録しておく _drawers.Add(typeof(SomeClass), new SomeClassDrawer()); } public static PropertyDrawer GetDrawer(System.Type fieldType) { PropertyDrawer drawer; return _drawers.TryGetValue(fieldType, out drawer) ? drawer : null; } }
Typeを与えれば適したPropertyDrawer
を返してくれます。
次にアトリビュートのPropertyDrawer
を下記のように変えます。
[CustomPropertyDrawer(typeof(ExampleAttribute))] public class ExampleAttributeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var drawer = PropertyDrawerDatabase.GetDrawer(fieldInfo.FieldType); drawer.OnGUI(position, property, label); } public override float GetPropertyHeight (SerializedProperty property, GUIContent label) { var drawer = PropertyDrawerDatabase.GetDrawer(fieldInfo.FieldType); return drawer.GetPropertyHeight(property, label); } }
Drawerを先ほどのPropertyDrawerDatabase
から取得するようにしました。
以上で対応は完了です。
できないことと参考
下記の記事でやっているようなproperty情報のキャッシュはできないです。(やるとnullが発生しました)
もうちょっとしっかりとした実装をしたければ、下記のアセットを参考にしたり、
やりたい事次第では導入を検討するのがよさそうです。