Get type of the object where attribute was declared – Hide In Derived Inspector Attribute

Get type of the object where attribute was declared – Hide In Derived Inspector Attribute

I want to implement a custom Unity inspector attribute, like usual ones [HideInInspector] or [Range(0, 100f)]. 
This attribute should hide a field from the inspector if it's inherited from a base type, rather being defined directly in the type of the object we're inspecting.
So, for example, if I have these classes...
public class Enemy : MonoBehaviour {
    [SuperField]
    public int EnemyHealth; 
}

public class GiantMonster : Enemy {
    [SuperField]
    public int Armor;
}

then the inspector for a base Enemy should show a control for the EnemyHealth variable, but when inspecting a GiantMonster we should not see a control for this variable.
So far I have my attribute: 
public class SuperFieldAttribute : PropertyAttribute {}

which has its own PropertyDrawer:
[CustomPropertyDrawer(typeof(SuperFieldAttribute))]
public class SuperFieldDrawer : PropertyDrawer {

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // ...
    }
} 

Inside OnGUI I need to access/find the type where the field we're drawing was declared with the [SuperFieldAttribute]. 
So if SuperFieldDrawer.OnGUI() is called for the SerializedProperty corresponding to GiantMonster.EnemyHealth, I should be able to determine that the type where EnemyHealth was declared is Enemy and not GiantMonster. (I'll use this to hide the field so it's not drawn)
And if SuperFieldDrawer.OnGUI() is called for the SerializedProperty corresponding to GiantMonster.Armor, I should be able to determine that the type where Armor was declared is GiantMonster itself. (Meaning I should proceed and draw the field)
So far I've found ways to access the type of the component we're inspecting, but not the type where the field was first declared, so that's the missing piece I need to fill in.
It's OK if I use reflection and so on; any method is good, performance isn't a concern. 

Solutions/Answers:

Answer 1:

As luck would have it, the PropertyDrawer‘s fieldInfo has a handy DeclaringType property that tells us exactly where the field was defined, without needing to walk the inheritance hierarchy ourselves – at least for fields directly on the object we’re inspecting. As we discovered in the comments, we still need to do a bit of manual walking for fields in a child object or struct.

Here’s how we can implement that [HideInDerived] attribute drawer using this:

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(HideInDerivedAttribute))]
public class HideInDerivedDrawer : PropertyDrawer {

    bool? _chachedIsDerived;
    // Cache this so we don't have to muck with strings 
    // or walk the type hierarchy on every repaint.
    bool IsDerived(SerializedProperty property) {        
        if (_chachedIsDerived.HasValue == false) {

            string path = property.propertyPath;
            var type = property.serializedObject.targetObject.GetType();

            if (path.IndexOf('.') > 0) {
                // Field is in a nested type. Dig down to get that type.
                var fieldNames = path.Split('.');
                for(int i = 0; i < fieldNames.Length - 1; i++) {
                    var info = type.GetField(fieldNames[i]);
                    if (info == null)
                        break;
                    type = info.FieldType;
                }
            }

            _chachedIsDerived = fieldInfo.DeclaringType != type;
        }
        return _chachedIsDerived.Value; 
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // If we're in a more derived type than where this field was declared,
        // abort and draw nothing instead.
        if (IsDerived(property))           
            return;

        EditorGUI.PropertyField(position, property, label, true);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {

        if (IsDerived(property)) {
            // Collapse the unseen derived property.
            return -EditorGUIUtility.standardVerticalSpacing;            
        } else {
            // Provision the normal vertical spacing for this control.
            return EditorGUI.GetPropertyHeight(property, label, true);
        }
    }
}

Known issues

  • This does not stack with other property drawing attributes like [Range(min, max]. Only one attribute will take effect, depending on their order. Internally it seems Unity tracks only one inspector attribute per field.

    It may be possible with some heavy-duty reflection to identify other attributes on the field with their own CustomPropertyDrawers, and construct a corresponding PropertyDrawer to delegate the drawing to, but it would get messy fast. It might be simpler to make a combined attribute like [HideInDerivedRange(min, max)] for specific cases.

  • Types with their own custom property drawer will fall back to the default drawing style if marked [HideInDerived]. It works correctly for all of Unity’s built-in property widgets though, showing colour pickers and vector/object fields as expected.

  • This does not correctly hide the label, fold-out, and size controls of an Array or List, although it does hide the collection’s contents.

    As Candid Moon found, this is due to a change in Unity 4.3 to allow Attribute-targeted PropertyDrawers to apply to each element in a collection, which unfortunately makes it impossible to target the array itself in the present version.

    A workaround suggested by slippdouglas here is to define a serializable struct containing your collection, so that the [HideInDerived] attribute hides the struct as a whole, like so:

.

[System.Serializable] 
public struct HideableStringList {
     public List<string> list;

     // Optional, for convenience, so the rest of your code can
     // continue to behave like this is just a List<string>
     // without always referencing the .list field.
     public static implicit operator List<string>(HideableStringList c) {
         return c.list;
     }
     public static implicit operator HideableStringList(List<string> l) {
         return new HideableStringList(){ list = l }; 
     }
 }

Because of the way Unity draws the inspector, we can’t use a single generic type to handle all these cases, so you’ll have to copy this boilerplate for each array type (yuck). But at least it lets this work:

[HideInDerived]
HideableStringList myStrings = new List<string>(3);

References