Custom Class And Method Attributes For Inspector

Hi guys,

I want to create a custom attribute for the Unity Inspector. Similar to how Attributes and PropertyDrawers work, but on a class level. Specifically, I want to create a custom attribute I can add to a MonoBehaviour class or one of it’s methods, which creates a button at the top the Inspector. Triggering the method. Is this something possible or would I have to make some hacky solution of some base class for everything and go through reflection on every class looking for method attributes?

There’s no built-in way to decorate methods or classes and have them customize the inspector the way that property drawers do. You can create your own MonoBehaviour Editor …

[CustomEditor(typeof(MonoBehaviour), true)]

… to avoid creating your own base MyBaseMonoBehaviour class, but there’s a good chance it might break some Unity built-in types (a lot of them have custom editors) or your own editors. This may or may not prove to be an issue for you.

You’re stuck with using reflection to find the method and class attributes though. I wouldn’t describe that as “hacky”. I personally went with the custom base class, but I use a lot of attributes and other operations to speed up development.

You’re not finding it too much of a pull on the CPU in the Editor then? Sounds like I’ll just go with that then, thanks!

I only have an [InspectorButton] attribute that affects drawing the Inspector GUI, but no, no performance issues from it.

public override void OnInspectorGUI()
{
   DrawDefaultInspector();

   IEnumerable<MethodInfo> methods = target.GetType()
       .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
       .Where(m => m.GetParameters().Length == 0);

   foreach (MethodInfo method in methods)
   {
       InspectorButtonAttribute buttonAttribute = Attribute.GetCustomAttribute(method, typeof(InspectorButtonAttribute)) as InspectorButtonAttribute;

       if (buttonAttribute != null)
       {
           if (GUILayout.Button(buttonAttribute.label) == true)
           {
               foreach (UnityObject methodTarget in targets)
                   method.Invoke(methodTarget, null);
           }
       }
   }
}

The rest of my attributes are mainly used during Reset() and OnValidate(), and so happen incredibly infrequently. Your mileage may vary.

2 Likes

Yeah I think that’s probably the way to go. The only other alternate I could think of would be to make some custom property drawers for fields containing UnityEvent’s, and triggering them on button press. Like this:

[System.Serializable]
[ExecuteInEditMode]
public class InspectorButton
{
    [SerializeField] private string name;
    [SerializeField] private UnityEvent onPress;

    public string Name { get => name; }

    public void Press()
    {
        onPress.Invoke();
    }
}

[CustomPropertyDrawer(typeof(InspectorButton))]
    public class InspectorButtonDrawer : PropertyDrawer
    {
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return EditorGUI.GetPropertyHeight(property, label);
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.PropertyField(position, property, label, true);

            var btn = (InspectorButton)fieldInfo.GetValue(property.serializedObject.targetObject);
            if (GUILayout.Button(btn.Name))
            {
                btn.Press();
            }

            EditorGUILayout.Space();
        }
    }

Implementing it like this:

#if UNITY_EDITOR
        [SerializeField] private InspectorButton resetButton;
#endif

Still not sure which way to go.