Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Custom Class And Method Attributes For Inspector

Discussion in 'Scripting' started by Nigey, Oct 21, 2021.

  1. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    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?
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    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 ...
    Code (csharp):
    1. [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.
     
  3. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    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!
     
  4. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    I only have an [InspectorButton] attribute that affects drawing the Inspector GUI, but no, no performance issues from it.

    Code (csharp):
    1.  
    2. public override void OnInspectorGUI()
    3. {
    4.    DrawDefaultInspector();
    5.  
    6.    IEnumerable<MethodInfo> methods = target.GetType()
    7.        .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
    8.        .Where(m => m.GetParameters().Length == 0);
    9.  
    10.    foreach (MethodInfo method in methods)
    11.    {
    12.        InspectorButtonAttribute buttonAttribute = Attribute.GetCustomAttribute(method, typeof(InspectorButtonAttribute)) as InspectorButtonAttribute;
    13.  
    14.        if (buttonAttribute != null)
    15.        {
    16.            if (GUILayout.Button(buttonAttribute.label) == true)
    17.            {
    18.                foreach (UnityObject methodTarget in targets)
    19.                    method.Invoke(methodTarget, null);
    20.            }
    21.        }
    22.    }
    23. }
    24.  
    The rest of my attributes are mainly used during Reset() and OnValidate(), and so happen incredibly infrequently. Your mileage may vary.
     
    Nigey likes this.
  5. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    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:

    Code (CSharp):
    1. [System.Serializable]
    2. [ExecuteInEditMode]
    3. public class InspectorButton
    4. {
    5.     [SerializeField] private string name;
    6.     [SerializeField] private UnityEvent onPress;
    7.  
    8.     public string Name { get => name; }
    9.  
    10.     public void Press()
    11.     {
    12.         onPress.Invoke();
    13.     }
    14. }
    15.  
    16. [CustomPropertyDrawer(typeof(InspectorButton))]
    17.     public class InspectorButtonDrawer : PropertyDrawer
    18.     {
    19.         public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    20.         {
    21.             return EditorGUI.GetPropertyHeight(property, label);
    22.         }
    23.  
    24.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    25.         {
    26.             EditorGUI.PropertyField(position, property, label, true);
    27.  
    28.             var btn = (InspectorButton)fieldInfo.GetValue(property.serializedObject.targetObject);
    29.             if (GUILayout.Button(btn.Name))
    30.             {
    31.                 btn.Press();
    32.             }
    33.  
    34.             EditorGUILayout.Space();
    35.         }
    36.     }
    37.  
    Implementing it like this:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.         [SerializeField] private InspectorButton resetButton;
    3. #endif
    Still not sure which way to go.