Skip to main content
Improvements & known issues
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401

As luck would have it, the FieldInfoPropertyDrawer type's fieldInfo has a handy DeclaringType property that tells us exactly where itthe field was defined, without needing to walk the inheritance hierarchy ourselves - at least for fields directly on the object we're inspecting. And PropertyDrawers already have accessAs we discovered in the comments, we still need to that via their fieldInfo memberdo a bit of manual walking for fields in a child object or struct. :)

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 Get(_chachedIsDerived.HasValue the== typefalse) of{

 the object we're editing        string path = property.propertyPath;
            var type = property.serializedObject.targetObject.GetType();

            if (path.IndexOf('.') > 0) {
                // Is thisField fieldis fromin a morenested ancestraltype. Dig down to get that type.
 than the one we're drawing?           var fieldNames = path.Split('.');
        return        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;DeclaringType != type;
        }
        return _chachedIsDerived.Value; 
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort ifIf we're in a more derived type than where this field was declared,
        // abort and draw nothing instead.
        if (IsDerived(property))
            return;
        
     return;
   // Otherwise, draw the control as we normally would.
        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:

Unfortunately.

[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, I haven't foundwe can't use a waysingle generic type to consistently get this playing nicely with other PropertyDrawer attributeshandle all these cases, like if you wanted a [Range]so you'll have to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the othercopy this boilerplate for each array type (yuck). But at least it lets this work:

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

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves. And PropertyDrawers already have access to that via their fieldInfo member. :)

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();
        // Is this field from a more ancestral type than the one we're drawing?
        return type != fieldInfo.DeclaringType;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Unfortunately, I haven't found a way to consistently get this playing nicely with other PropertyDrawer attributes, like if you wanted a [Range] to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the other.

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.

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);
Simplifying using fieldInfo member
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves. And PropertyDrawers already have access to that via their fieldInfo member. :)

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();
 
        // Retrieve info aboutIs thethis field we're drawing.
  from a more ancestral type than varthe infoone =we're type.GetField(property.name);
drawing?
        return info.DeclaringTypetype != type;fieldInfo.DeclaringType;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Unfortunately, I haven't found a way to consistently get this playing nicely with other PropertyDrawer attributes, like if you wanted a [Range] to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the other.

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves.

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();
 
        // Retrieve info about the field we're drawing.
        var info = type.GetField(property.name);

        return info.DeclaringType != type;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Unfortunately, I haven't found a way to consistently get this playing nicely with other PropertyDrawer attributes, like if you wanted a [Range] to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the other.

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves. And PropertyDrawers already have access to that via their fieldInfo member. :)

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();
        // Is this field from a more ancestral type than the one we're drawing?
        return type != fieldInfo.DeclaringType;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Unfortunately, I haven't found a way to consistently get this playing nicely with other PropertyDrawer attributes, like if you wanted a [Range] to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the other.

Correction - found some inconsistent behaviour.
Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves.

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();

        // Retrieve info about the field we're drawing.
        var info = type.GetField(property.name);

        return info.DeclaringType != type;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Note that if you're using multiple attributesUnfortunately, I haven't found a way to consistently get this playing nicely with property drawing logicother PropertyDrawer attributes, it seemslike if you wanted a [HideInDerived][Range] needs to come lastboth show with a slider control in order to override the othersbase class and keep them from drawingbe hidden in the derived class. So far I can only get one or the other.

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves.

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();

        // Retrieve info about the field we're drawing.
        var info = type.GetField(property.name);

        return info.DeclaringType != type;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Note that if you're using multiple attributes with property drawing logic, it seems [HideInDerived] needs to come last in order to override the others and keep them from drawing.

As luck would have it, the FieldInfo type has a handy DeclaringType property that tells us exactly where it was defined, without needing to walk the inheritance hierarchy ourselves.

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

using UnityEngine;
using UnityEditor;

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

    bool IsDerived(SerializedProperty property) {
        // Get the type of the object we're editing.
        var type = property.serializedObject.targetObject.GetType();

        // Retrieve info about the field we're drawing.
        var info = type.GetField(property.name);

        return info.DeclaringType != type;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Abort if we're in a more derived type than where this field was declared    
        if (IsDerived(property))
            return;
        
        // Otherwise, draw the control as we normally would.
        EditorGUI.PropertyField(position, property, label);
    }

    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);
        }
    }
}

Unfortunately, I haven't found a way to consistently get this playing nicely with other PropertyDrawer attributes, like if you wanted a [Range] to both show with a slider control in the base class and be hidden in the derived class. So far I can only get one or the other.

Source Link
DMGregory
  • 140.8k
  • 23
  • 257
  • 401
Loading