Hiding or Disabling inspector properties using PropertyDrawers within Unity

Blog_Unity5PropertyDrawers01_2015_11_29_Example05When working with unity and larger scripts that provide a lot of adjustable parameters, the inspector can quickly turn into a very unmanageable wall of text and entry fields.

So lets take a look at what can be done to make inspector navigation a much more enjoyable experience without having to write a custom inspector layout for your script.

In this post I quickly go over the use of HeaderAttributes to help you keep the inspector clean and easy to work with. And finally I explain how you can hide or disable inspector properties based on a bool field within your script.

Improving Basic Readability

Lets take a look at the following example inspector window.

Blog_Unity5PropertyDrawers01_2015_11_29_Example01

Notice that with even a small script like this it can already be difficult to get a good idea of what is going on.

What we would like is the ability to group our fields in a way that makes sense for the designer. Making it very clear what the role is of the different variables within the functionality of my script. This can be done with HeaderAttributes.

Simply add the attribute in front of  your public field and you are good to go. A simple, quick and super effective way to instantly increase the readability of your script within Unity’s editor.

    [Header("Auto Aim")]
    public bool EnableAutoAim = false;

Blog_Unity5PropertyDrawers01_2015_11_29_Example02

Unity provides a series of additional attributes that can help you in managing the look of your script within the inspector and how the user can interact with them. All without having to write special inspector specifically for this script.

Some useful and commonly used attributes to look into are the space, tooltip and range attributes.

Hide or Disable Fields based on user input

Using HeaderAttributes is all shiny and cool but what if we want to help the designer even further?

In this example most of the properties under the Auto Aim category will only be relevant when EnableAutoAim is set to true.
Say that, to avoid confusion and to keep our inspector nice and clean, we want to hide all the properties that are directly related to the EnableAutoAim if this property is set to false. Or instead of hiding them, we want to at least disable them, preventing user input.

Blog_Unity5PropertyDrawers01_2015_11_29_Example07

To make this possible we need to create a custom attribute and matching PropertyDrawer.
I’m not going to go into the details on how PropertyDrawers and Attributes fully work. For that I will point you towards https://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/property-drawers-custom-inspectors.

But I will go over the logic and setup of this specific PropertyDrawer and attribute.
Lets get started 🙂 .

Step 1: Create the scripts

As mentioned we are going to need 2 scripts: ConditionalHideAttribute and ConditionalHidePropertyDrawer.
Important to note is that the ConditionalHidePropertyDrawer needs to be placed inside an Editor folder within the project and the ConditionalHideAttribute scripts needs to be located outside this folder. Otherwise you will not be able to find and use the attribute.

Blog_Unity5PropertyDrawers01_2015_11_29_Example08

Step 2: ConditionalHideAttribute

First we create our Attribute. We want to be able to define a bool Field that will be in control of the hiding/enabling of the inspector properties and we also want to be able to choose between the hide and enable options.

The  ConditionalHideAttribute inherits from the PropertyAttribute class and is nothing more than a simple data class with some constructors. The main purpose of this class is to provide additional data that will be used within the PropertyDrawer. If you want to add extra options/ parameters to this attribute, this is where you would add them.

The AttributeUsage attributes used at the top of the ConditionalHideAttribute class control where you will be able to use this attribute.

using UnityEngine;
using System;
using System.Collections;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property |
    AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
public class ConditionalHideAttribute : PropertyAttribute
{
    //The name of the bool field that will be in control
    public string ConditionalSourceField = "";
    //TRUE = Hide in inspector / FALSE = Disable in inspector 
    public bool HideInInspector = false;

    public ConditionalHideAttribute(string conditionalSourceField)
    {
        this.ConditionalSourceField = conditionalSourceField;
        this.HideInInspector = false;
    }

    public ConditionalHideAttribute(string conditionalSourceField, bool hideInInspector)
    {
        this.ConditionalSourceField = conditionalSourceField;
        this.HideInInspector = hideInInspector;
    }
}

Step 3 : ConditionalHidePropertyDrawer

In the PropertyDrawer we need to do a couple of things.

  • When Unity wants to draw the property in the inspector we need to:
    • Check the parameters that we used in our custom attribute
    • Hide and/or disable the property that is being drawn based on the attribute parameters
  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        //get the attribute data
        ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
        //check if the propery we want to draw should be enabled
        bool enabled = GetConditionalHideAttributeResult(condHAtt, property);

        //Enable/disable the property
        bool wasEnabled = GUI.enabled;
        GUI.enabled = enabled;

        //Check if we should draw the property
        if (!condHAtt.HideInInspector || enabled)
        {
            EditorGUI.PropertyField(position, property, label, true);
        }

        //Ensure that the next property that is being drawn uses the correct settings
        GUI.enabled = wasEnabled;
    }

 

In order to check if the property should be enabled or not we call GetConditionalHideAttributeResult.

    private bool GetConditionalHideAttributeResult(ConditionalHideAttribute condHAtt, SerializedProperty property)
    {
        bool enabled = true;
        //Look for the sourcefield within the object that the property belongs to
         string propertyPath = property.propertyPath; //returns the property path of the property we want to apply the attribute to
        string conditionPath = propertyPath.Replace(property.name, condHAtt.ConditionalSourceField); //changes the path to the conditionalsource property path
        SerializedProperty sourcePropertyValue = property.serializedObject.FindProperty(conditionPath);

        if (sourcePropertyValue != null)
        {
            enabled = sourcePropertyValue.boolValue;
        }
        else
        {
            Debug.LogWarning("Attempting to use a ConditionalHideAttribute but no matching SourcePropertyValue found in object: " + condHAtt.ConditionalSourceField);
        }

        return enabled;
    }

 

  • Calculate the height of our property so that (when the property needs to be hidden) the following properties that are being drawn don’t overlap
  public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
        bool enabled = GetConditionalHideAttributeResult(condHAtt, property);

        if (!condHAtt.HideInInspector || enabled)
        {
            return EditorGUI.GetPropertyHeight(property, label);
        }
        else
        {
            //The property is not being drawn
            //We want to undo the spacing added before and after the property
            return -EditorGUIUtility.standardVerticalSpacing;
        }
    }

 

The complete ConditionalHidePropertyDrawer script then looks as follows:

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ConditionalHideAttribute))]
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ConditionalHideAttribute))]
public class ConditionalHidePropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
        bool enabled = GetConditionalHideAttributeResult(condHAtt, property);

        bool wasEnabled = GUI.enabled;
        GUI.enabled = enabled;
        if (!condHAtt.HideInInspector || enabled)
        {
            EditorGUI.PropertyField(position, property, label, true);
        }

        GUI.enabled = wasEnabled;
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
        bool enabled = GetConditionalHideAttributeResult(condHAtt, property);

        if (!condHAtt.HideInInspector || enabled)
        {
            return EditorGUI.GetPropertyHeight(property, label);
        }
        else
        {
            return -EditorGUIUtility.standardVerticalSpacing;
        }
    }

    private bool GetConditionalHideAttributeResult(ConditionalHideAttribute condHAtt, SerializedProperty property)
    {
        bool enabled = true;
        string propertyPath = property.propertyPath; //returns the property path of the property we want to apply the attribute to
        string conditionPath = propertyPath.Replace(property.name, condHAtt.ConditionalSourceField); //changes the path to the conditionalsource property path
        SerializedProperty sourcePropertyValue = property.serializedObject.FindProperty(conditionPath);

        if (sourcePropertyValue != null)
        {
            enabled = sourcePropertyValue.boolValue;
        }
        else
        {
            Debug.LogWarning("Attempting to use a ConditionalHideAttribute but no matching SourcePropertyValue found in object: " + condHAtt.ConditionalSourceField);
        }

        return enabled;
    }
}

Step 4: How to use

Using our newly created ConditionalHideAttribute is fairly straightforward and works much like the HeaderAttribute.

    [Header("Auto Aim")]
    public bool EnableAutoAim = false;

    [ConditionalHide("EnableAutoAim", true)]
    public float Range = 0.0f;
    [ConditionalHide("EnableAutoAim", true)]
    public bool AimAtClosestTarget = true; 
    [ConditionalHide("EnableAutoAim", true)]
    public DemoClass ExampleDataClass = new DemoClass();
    [ConditionalHide("EnableAutoAim", true)]
    public AnimationCurve AimOffsetCurve = new AnimationCurve();

Leaving out the second parameter or setting it to false draws the property using the enable/disable method.

    [Header("Resources")]
    public bool ConsumeResources = true;

    [ConditionalHide("ConsumeResources")]
    public bool DestroyOnResourcesDepleted = true;
    [ConditionalHide("ConsumeResources")]
    public float ResourceAmount = 100.0f;
    [ConditionalHide("ConsumeResources")]
    public DemoClass AnotherExampleDataClass = new DemoClass();

Some Caveats

This being Unity after all there are a few situations where this custom attribute will fail.
The first situation being Lists and arrays.
In those cases the property drawer will be applied to the content of the list but not on the list itself… .
You will still be able to adjust the number of items as well.
However when the List is part of a Seriazable class everything will behave as expected.

Why is this you ask? Because Unity, that’s why.

Blog_Unity5PropertyDrawers01_2015_11_29_Example09

Blog_Unity5PropertyDrawers01_2015_11_29_Example10
Another situation where the use of this attribute will fail is when being combined with different property drawers that change the way the property itself is being drawn such as the TextAreaAttribute.
This makes sense as this attribute performs his own OnGUI drawing and GetPropertyHeight calculation of the same property. The attribute that is called last will be the attribute that determines what is being drawn in this case.

Update 01 [15/03/2016]

Added support for 2 bools instead of only 1.
You can use this option by simply adding the second ConditionalSourceField as parameter.

    [ConditionalHide("Condition01", ConditionalSourceField2 = "Condition02")]

Update 02 [13/04/2016]

Based on a suggestion by @VeevTSG via Twitter I have added support for object references to be used as a condition.
You can easily extend the code to add additional condition checks such as integers, floats, strings, etc.

Update 03 [17/12/2016]

Added support for conditional property hiding in nested/encapsulated serializable objects that are used as inspector properties.
Thanks CrashOverride for the great addition in the comments!!
The downloadable file and article above have both been updated with the new addition.

Update 04 [01/08/2018]

It has been a while since I updated the source for this so I have added some functionality that is part of the current iteration that I’m using in my day to day work.

  • Added the option to inverse the results of the different ConditionalSourceFields individually as well as inverting the full end result of the conditional check.
  • Added the option to use a logic OR check instead of a logic AND check.
  • Added the option to define ConditionalSourceField arrays (both for the properties and per property result inverse bool). This will allow you to have as many properties as you want to be considered for the hide.

 

Also be sure to have a look at tertle’s nice extention of the ConditionalHideAttribute : https://www.brechtos.com/hiding-or-disabling-inspector-properties-using-propertydrawers-within-unity-5/#comment-1359

That’s it

I hope this post was off some use and be sure to let me know if it was :).
You can download the 2 script files directly using the following link: ConditionalHideAttribute.rar

72 Responses

    1. It should work just fine with any attribute that doesn’t directly alter the property visualization itself.
      So things such as tool tip, space and header will behave as expected.
      Unfortunately I haven’t found a way to automatically combine custom PD’s.

  1. This sort of thing would be extremely useful for my project. However, I am writing it entirely in JS and would need to convert these scripts into JS for them to work with my scripts.

    Is that even possible? I am having a hard time trying to find the JS equivalent lines online. Would you be able to point me towards the Unity Script Reference if it exists?

    1. Well, I’ve been able to successfully convert the scripts into JS, but the ConditionalHidePropertyDrawer is having a hard time getting the attribute from the ConditionalHideAttribute script, giving me a null reference exception error when I have the object I wish to use this attribute on selected.

      Any words of wisdom? I’ve got the script in the Editor folder and even tried directly grabbing the attribute through Resources.Load via its script.

      1. Hi Max.

        I personally have no experience with JS inside of unity but I do now that instead of [ConditionalHide] (c#) you need to use @ConditionalHide when using attributes in JS (https://docs.unity3d.com/Manual/Attributes.html)

        Technically, as far as I know, you could even keep the custom propertyattribute in c# within the project and still use it inside of your JS scripts with the @ symbol. But I could be wrong on this one.

        I hope this was the the missing element to get it to work 🙂

        1. Thanks for your response. While I wasn’t able to get the converted JS scripts to function correctly I did end up realising that I could just use the C# scripts.

          I haven’t had much experience with having the two languages work together in Unity so I just assumed it was safer to convert it. I’ve grabbed the attribute with the JS syntax and it all works as it should.

          Thanks, Brechtos.

          1. Hey!

            How did you manage to get it to work from a javascript?

            The thing works just fine from a CS script with “[ConditionalHide(“test”)]”

            But it doesn’t work from unityscript files. I use it like tihs:

            @ConditionalHide(“test”)

            But I get an Unity error: “No attribute with the name ‘ConditionalHide’ or ‘ConditionalHideAttribute’ was found (attribute names are case insensitive). Did you mean ‘UnityEditor.CanEditMultipleObjects’?”

          2. Hey Beldarak,

            As far as I know, if it works in a c# script within your project it should also work for a javascript script.
            @ConditionalHide (“PropertyName”)

            It seemed to work for Max.
            Unfortunatly I have no experience with using Javascript in unity. I’m not sure if the space after the @attribute is important or not but all the unity samples on the site do seem to have that. Let me know if that solves your issue 🙂

  2. I found this very useful, thanks.

    For my own purposes I have extended it to test against floats/ints/and array size. It defaults to test these as > 0 but I also added TestValue public fields which can be specified to test against instead.

    in ConditionalHideAttribute:

    public bool TestValueBool = true;
    public int TestValueInt = 0;
    public float TestValueFloat = 0;

    and then in the test in the PropertyDrawer:

    if (sourcePropertyValue.isArray)
    return sourcePropertyValue.arraySize > condHAtt.TestValueInt;

    switch (sourcePropertyValue.propertyType)
    {
    case SerializedPropertyType.Integer:
    return sourcePropertyValue.intValue > condHAtt.TestValueInt;

    case SerializedPropertyType.Float:
    return sourcePropertyValue.floatValue > condHAtt.TestValueFloat;

    case SerializedPropertyType.Boolean:
    return sourcePropertyValue.boolValue == condHAtt.TestValueBool;


    }

    I cannot find a way to make it hide arrays properly, OnGUI appears to never receive the actual array property only the individual elements… stupid unity.

    1. Unfortunately there is indeed no clean way to hide arrays. Well… it will hide everything except the index count 🙁 .
      So far I haven’t found a way to work around this and I’m not sure if there is. It does hide custom data classes though, so an array wrapped in a custom serializable class will hide as part of the class. But that’s a bit to hacky and overkill for my taste 🙂 .

      Nice to see you were able to extend it to fit your needs. I personally also have a variation that allows you to hide based on enums etc. But for those I usually just create a completely new attribute to avoid adding to much functionality to the base ConditionalHideAttribute implementation and making it to overloaded/complex to use.

  3. Good news,

    After looking deep into this I managed to tweak something really generic, working for both encapsulated/non encapsulated parameters. The key reside in propertyPath. Here’s how I did:

        private bool GetConditionalHideAttributeResult(ConditionalHideAttribute condHAtt, SerializedProperty property)
        {
            bool enabled = true;
    
    ---> MODIFICATION START
    		string pathProp = property.propertyPath;
    		string conditionPath = pathProp.Replace(property.name, condHAtt.ConditionalSourceField);
    
    		SerializedProperty sourcePropertyValue = property.serializedObject.FindProperty(conditionPath);
    -----> MODIFICATION END
    
    		//SerializedProperty sourcePropertyValue = property.serializedObject.FindProperty(condHAtt.ConditionalSourceField);
            if (sourcePropertyValue != null)
            {
                enabled = CheckPropertyType(sourcePropertyValue);               
            }
            else
            {
                Debug.LogWarning("Attempting to use a ConditionalHideAttribute but no matching SourcePropertyValue found in object: " + condHAtt.ConditionalSourceField);
            } 
    

    Thing is serializedObject seem to always refer to the root were the variable exist, therefore being problematic when using subclasses.
    Here’s the basic subclass I was trying to use:

    public class PotatoScriptTest : MonoBehaviour
    {
    	[System.Serializable]
    	public class rigTest
    	{
    		public bool enable;
    
    		[ConditionalHideAttribute("enable", true)]
    		public string name = "Test";
    		[ConditionalHideAttribute("enable", true)]
    		public float speed = 2.0f;
    	};
    
    	public rigTest blabla;
    
    	public rigTest[] blablaArray = new rigTest[5];
    }
    
    1. Thx for the great addition CrashOverride !
      Updated the article and downloadable file with the fix/upgrade.
      Hadn’t really needed this myself yet but was probably only a matter of time 🙂

      1. Nevermind, I adjusted the script 🙂 But I can’t get the ConditionalSourceField2 to work…

        I got this code:

        [ConditionalHide(“NumberOfRotations”,ConditionalSourceField2 = “RotationType”,true,true)]

        RotationType is an enum, that might have something to do with it…

        1. Hi Alexander,
          Glad you found use for the attribute 🙂
          Yes you can use the script in an asset but it would be nice to keep the originally made by header and perhaps a mention+link in the credits 🙂

          As for the enum. Enums are actually just integer values so this will not work out of the box just like that.
          I do have an alternate version of this system that works with enums and allows me to hide properties based on the value of an enum.

          Below is the code for that. You could easily combine the 2 and create different variations but for our use it works just having both version.

              using UnityEngine;
          using System;
          using System.Collections;
          
          //Original version of the ConditionalEnumHideAttribute created by Brecht Lecluyse (www.brechtos.com)
          //Modified by: -
          
          [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property |
              AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
          public class ConditionalEnumHideAttribute : PropertyAttribute
          {
              //The name of the bool field that will be in control
              public string ConditionalSourceField = "";
          
              public int EnumValue1 = 0;
              public int EnumValue2 = 0;
          
              public bool HideInInspector = false;
              public bool Inverse = false;
          
              public ConditionalEnumHideAttribute(string conditionalSourceField, int enumValue1)
              {
                  this.ConditionalSourceField = conditionalSourceField;
                  this.EnumValue1 = enumValue1;
                  this.EnumValue2 = enumValue1;
              }
          
              public ConditionalEnumHideAttribute(string conditionalSourceField, int enumValue1, int enumValue2)
              {
                  this.ConditionalSourceField = conditionalSourceField;
                  this.EnumValue1 = enumValue1;
                  this.EnumValue2 = enumValue2;
              }
          }
          
          using UnityEngine;
          using UnityEditor;
          
          //Original version of the ConditionalEnumHideAttribute created by Brecht Lecluyse (www.brechtos.com)
          //Modified by: -
           
          [CustomPropertyDrawer(typeof(ConditionalEnumHideAttribute))]
          public class ConditionalEnumHidePropertyDrawer : PropertyDrawer
          {
              public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
              {
                  ConditionalEnumHideAttribute condHAtt = (ConditionalEnumHideAttribute)attribute;
                  int enumValue = GetConditionalHideAttributeResult(condHAtt, property);
           
                  bool wasEnabled = GUI.enabled;
                  GUI.enabled = ((condHAtt.EnumValue1 == enumValue) || (condHAtt.EnumValue2 == enumValue));
                  if (!condHAtt.HideInInspector || (condHAtt.EnumValue1 == enumValue) || (condHAtt.EnumValue2 == enumValue))
                  {
                      EditorGUI.PropertyField(position, property, label, true);
                  }
           
                  GUI.enabled = wasEnabled;
              }
           
              public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
              {
                  ConditionalEnumHideAttribute condHAtt = (ConditionalEnumHideAttribute)attribute;
                  int enumValue = GetConditionalHideAttributeResult(condHAtt, property);
          
                  if (!condHAtt.HideInInspector || (condHAtt.EnumValue1 == enumValue) || (condHAtt.EnumValue2 == enumValue))
                  {
                      return EditorGUI.GetPropertyHeight(property, label);
                  }
                  else
                  {
                      return -EditorGUIUtility.standardVerticalSpacing;
                  }
              }
          
              private int GetConditionalHideAttributeResult(ConditionalEnumHideAttribute condHAtt, SerializedProperty property)
              {
                  int enumValue = 0;
          
                  SerializedProperty sourcePropertyValue = null;
                  //Get the full relative property path of the sourcefield so we can have nested hiding
                  if (!property.isArray)
                  {
                      string propertyPath = property.propertyPath; //returns the property path of the property we want to apply the attribute to
                      string conditionPath = propertyPath.Replace(property.name, condHAtt.ConditionalSourceField); //changes the path to the conditionalsource property path
                      sourcePropertyValue = property.serializedObject.FindProperty(conditionPath);
          
                      //if the find failed->fall back to the old system
                      if (sourcePropertyValue == null)
                      {
                          //original implementation (doens't work with nested serializedObjects)
                          sourcePropertyValue = property.serializedObject.FindProperty(condHAtt.ConditionalSourceField);
                      }
                  }
                  else
                  {
                      //original implementation (doens't work with nested serializedObjects)
                      sourcePropertyValue = property.serializedObject.FindProperty(condHAtt.ConditionalSourceField);
                  }
          
          
                  if (sourcePropertyValue != null)
                  {
                      enumValue = sourcePropertyValue.enumValueIndex;
                  }
                  else
                  {
                      //Debug.LogWarning("Attempting to use a ConditionalHideAttribute but no matching SourcePropertyValue found in object: " + condHAtt.ConditionalSourceField);
                  }
          
                  return enumValue;
              }
          
          }
          
          1. Alright, that script is pretty similar to what I did 🙂

            Thanks!

            For the credits: your name will be clearly visible and I’ll put a link to your website in the asset description

            Have a wonderful day!

            Alex

          2. Hey, me again.

            How can I make multiple attributes work alongside each other?

            for example:

            [ConditionalHide(“DrawGizmo”,true)]
            [Range(0.0F, 1.0F)]
            public float GizmoColorAlpha = 0.25F; //Opacity.

            Thanks!

          3. As far as I know this is currently not possible with PropertyDrawers in Unity . I wish it was.
            You can however stack decorator attributes as they don’t change the visualization of the data.

          4. Made a custom version so that you can do that:

             
            public class ConditionalHideAttribute : PropertyAttribute
            {
                public struct Range
                {
                    public float min;
                    public float max;
            
                    public Range(float min, float max)
                    {
                        this.min = min;
                        this.max = max;
                    }
                }
            
                public string ConditionalSourceField = "";
                public string ConditionalSourceField2 = "";
                public bool HideInInspector = false;
                public bool Inverse = false;
                public Range range;
            
                public ConditionalHideAttribute(string conditionalSourceField = "", bool hideInInspector = false, bool inverse = false, float min = 0, float max = 0)
                {
                    this.ConditionalSourceField = conditionalSourceField;
                    this.HideInInspector = hideInInspector;
                    this.Inverse = inverse;
                    this.range = new Range(min, max);
                }
            
             
            public class ConditionalHidePropertyDrawer : PropertyDrawer
            {
                public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
                {
                    
                    ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
                    bool enabled = GetConditionalHideAttributeResult(condHAtt, property);        
            
                    bool wasEnabled = GUI.enabled;
                    GUI.enabled = enabled;
                    if (!condHAtt.HideInInspector || enabled)
                    {
                        if (condHAtt.range.min == condHAtt.range.max)
                        {
                            EditorGUI.PropertyField(position, property, label, true);
                        } else {
                            // Draw the property as a Slider or an IntSlider based on whether it's a float or integer.
                            if (property.propertyType == SerializedPropertyType.Float)
                                EditorGUI.Slider(position, property, condHAtt.range.min, condHAtt.range.max, label);
                            else if (property.propertyType == SerializedPropertyType.Integer)
                                EditorGUI.IntSlider(position, property, Convert.ToInt32(condHAtt.range.min), Convert.ToInt32(condHAtt.range.max), label);
                            else
                                EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
                        }
                    }        
            
                    GUI.enabled = wasEnabled;
                }
            
  4. I wanted to be able to add some more complex logic so I made a quick extension with reflection that let’s you check properties (that unity can’t serialize).

    I modified the drawer like so

    if (sourcePropertyValue != null)
            {
                enabled = CheckPropertyType(sourcePropertyValue);               
            }
            else
            {
                var propertyInfo = property.serializedObject.targetObject.GetType().GetProperty(conditionPath,
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    
                if (propertyInfo != null)
                {
                    var value = propertyInfo.GetValue(property.serializedObject.targetObject);
                    enabled = CheckPropertyType(value);
                }
            }
    
    ...
    
        private static bool CheckPropertyType(object val)
        {
            if (val is bool)
            {
                return (bool) val;
            }
            return true;
        }
    

    Then you can make properties with logic in them to conditionally show

            [SerializeField] private Space _space = Space.Screen;
            private bool IsScreenSpace => _space == Space.Screen;
    
            [ConditionalHide("IsScreenSpace", true)]
            [SerializeField]
            private Position _position = Position.Bottom;
    

    This is just a simple enum example, but you can do any combination of logic.

    1. Modified that slightly, but came to share. Had a case where I was using this on a serialized class in a list of the main class ie:

      ClassA {
          [Serialized]
          ClassB {
              [ConditionalHide("Getter", true, false)]
              public var variable;
      
              public Getter { get { return blah; }}
          }
          public List<ClassB> list;
      }
      
      if (sourcePropertyValue != null)
              {
                  enabled = CheckPropertyType(sourcePropertyValue);               
              } else 
              {
                  object propertyParent = GetParentObject(property);
      
                  enabled = (bool)(propertyParent.GetType().GetProperty(condHAtt.ConditionalSourceField)
                                   .GetValue(propertyParent, new object[0]));
              }
      
      public static object GetParentObject(SerializedProperty property)
          {
              string[] path = property.propertyPath.Split('.');
      
              object propertyObject = property.serializedObject.targetObject;
              object propertyParent = null;
              for (int i = 0; i < path.Length; ++i)
              {
                  if (path[i] == "Array")
                  {
                      int index = (int)(path[i + 1][path[i + 1].Length - 2] - '0');
                      propertyObject = ((IList)propertyObject)[index];
                      ++i;
                  }
                  else
                  {
                      propertyParent = propertyObject;
                      propertyObject = propertyObject.GetType().GetField(path[i]).GetValue(propertyObject);
                  }
              }
      
              return propertyParent;
          }
      
      1. Sweet. Thx for coming back and giving the update. Will give this one a try when I have some time 🙂

  5. Big thanks to you! Your script make my variable in inspector more clean and beauty! Hope you will share more knowledge about Unity!

    1. Thx and you’re welcome 🙂
      I indeed should do more of these but it’s mainly a time management issue. So many things to do! 🙂

  6. Very nice. Thanks for sharing.

    In case someone is using things and can’t build their project, in C#, you should wrap the PropertyDrawer in a #if UNITY_EDITOR region.

    1. This shouldn’t really be an issue as Unity should should already strip all of this because you are using editor classes 😉

    1. Hi Jared,

      Sorry for the late response. It seems something went funny with my mail notification settings.

      The order doesn’t matter. And yes you can have one true and one false, with some minor code changes. I actually added this in my current version of the script. I shall see if I can find some time to add the updated version later this week.

  7. Great work. Its been very handy for me.
    Currently I am trying to hide a list from the inspector but using this method, the list seems to be unaffected. Do you know of a way to hide a list entirely not just is elements?

    1. Unfortunately not 🙁
      I have tried to solve this a number of times but so far have had no luck in finding a solution. It annoys me and well and seems to be a limitation of Unity and attribute drawers.
      A somewhat nasty workaround is to wrap your list/array in a custom data class as those can be be fully hidden/toggled.

  8. To avoid the maintenance nightmare, use `nameof(MyProperty)` instead of `”MyProperty”`. This way you can rename your properties safely and they update accordingly instead of just not working anymore without telling it.

  9. Hi Guy and Brechtos I have Update!

    1, You can Hide Fields in Subclass,Derivated class
    2, now you can enum, object, string… to compare no only bool on source fields

    Example 1

    class FirstClass ()
    {
         public SecondClass FieldName;
    }
    
     class SecondClass()
    {
             public bool Enable;
             [ConditionalHide("Enable")]
             public string TestedField = "Hello World";
    }
    

    Example 2

    [ConditionalHide("Enable", Enum.ITEM_SECOND)] // not only
    

    Modifications:

    namespace Helper
    {
        [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property |
                        AttributeTargets.Class | AttributeTargets.Struct )]
        public class ConditionalHideAttribute : PropertyAttribute
        {
            public string SourceField;
            public bool HideInInspector;
            public bool Inverse;
            public object CompareValue;
    
            public ConditionalHideAttribute(string sourceField, object compareObject , bool inverse=false, bool hideInInspector = true)
            {
                this.SourceField = sourceField;
                this.HideInInspector = hideInInspector;
                this.Inverse = inverse;
                this.CompareValue = compareObject==null?true : compareObject;
            }
    
            public ConditionalHideAttribute(string sourceField, bool compareValue = true, bool inverse = false, bool hideInInspector = true)
            {
                this.SourceField = sourceField;
                this.HideInInspector = hideInInspector;
                this.Inverse = inverse;
                this.CompareValue = compareValue;
            }
        }
    }
    
    
    #if UNITY_EDITOR
    using System;
    using Assets.Scripts.Core.EditorUtils.EditorAtributes;
    using UnityEditor;
    using UnityEngine;
    
    
    namespace Helper
    {
        [CustomPropertyDrawer(typeof(ConditionalHideAttribute))]
        public class ConditionalHidePropertyDrawer : PropertyDrawer
        {
            public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
            {
                ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
                bool enabled = GetConditionalHideAttributeResult(condHAtt, property);
    
                bool wasEnabled = GUI.enabled;
                GUI.enabled = enabled;
                if (!condHAtt.HideInInspector || enabled)
                {
                    EditorGUI.PropertyField(position, property, label, true);
                }
    
                GUI.enabled = wasEnabled;
            }
    
            public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
            {
                ConditionalHideAttribute condHAtt = (ConditionalHideAttribute)attribute;
                bool enabled = GetConditionalHideAttributeResult(condHAtt, property);
    
                if (!condHAtt.HideInInspector || enabled)
                {
                    return EditorGUI.GetPropertyHeight(property, label);
                }
                else
                {
                    return -EditorGUIUtility.standardVerticalSpacing;
                }
    
            }
    
            private bool GetConditionalHideAttributeResult(ConditionalHideAttribute condHAtt, SerializedProperty propertyA)
            {
                bool enabled = true;
    
    
                SerializedProperty sourcePropertyValue = FindSerializableProperty(condHAtt, propertyA);
    
                if (sourcePropertyValue != null)
                {
                    var fieldValue = GetPropertyValue(sourcePropertyValue);
    
                    var comparingValue = condHAtt.CompareValue.ToString();
                    var fieldValueString = fieldValue.ToString();
    
                    enabled = comparingValue == fieldValueString;
                }
                else
                {
                    Debug.LogWarning("Attempting to use a ConditionalHideAttribute but no matching SourcePropertyValue found in object: " + condHAtt.SourceField);
                }
    
                if (condHAtt.Inverse) enabled = !enabled;
    
                return enabled;
            }
    
            private SerializedProperty FindSerializableProperty(ConditionalHideAttribute condHAtt, SerializedProperty property)
            {
                string propertyPath = property.propertyPath;
                int idx = propertyPath.LastIndexOf('.');
                if (idx == -1) 
                {
                    return property.serializedObject.FindProperty(condHAtt.SourceField);
                }
                else 
                {
                    propertyPath = propertyPath.Substring(0, idx);
                    return property.serializedObject.FindProperty(propertyPath).FindPropertyRelative(condHAtt.SourceField);
                }
            }
    
            public static object GetPropertyValue(SerializedProperty prop)
            {
                switch (prop.propertyType)
                {
                    case SerializedPropertyType.Integer:
                        return prop.intValue;
                    case SerializedPropertyType.Boolean:
                        return prop.boolValue;
                    case SerializedPropertyType.Float:
                        return prop.floatValue;
                    case SerializedPropertyType.String:
                        return prop.stringValue;
                    case SerializedPropertyType.Color:
                        return prop.colorValue;
                    case SerializedPropertyType.ObjectReference:
                        return prop.objectReferenceValue;
                    case SerializedPropertyType.LayerMask:
                        return (LayerMask)prop.intValue;
                    case SerializedPropertyType.Enum:
                        if (prop.enumNames.Length == 0) { return "undefined"; }
                        return prop.enumNames[prop.enumValueIndex];
                    case SerializedPropertyType.Vector2:
                        return prop.vector2Value;
                    case SerializedPropertyType.Vector3:
                        return prop.vector3Value;
                    case SerializedPropertyType.Vector4:
                        return prop.vector4Value;
                    case SerializedPropertyType.Rect:
                        return prop.rectValue;
                    case SerializedPropertyType.ArraySize:
                        return prop.arraySize;
                    case SerializedPropertyType.Character:
                        return (char)prop.intValue;
                    case SerializedPropertyType.AnimationCurve:
                        return prop.animationCurveValue;
                    case SerializedPropertyType.Bounds:
                        return prop.boundsValue;
                    case SerializedPropertyType.Gradient:
                        return null;
                    //throw new System.InvalidOperationException("Can not handle Gradient types.");
                }
    
                return null;
            }
        }
    }
    
    1. Thanks for sharing Michal.
      I’ve updated your comment so it uses the [codelanguage title=”Code snippet header“] and [/codelanguage] tags to make it easier for people to read the code you posted 😉

  10. Great work! Thank you!

    Since it can’t hide the array and lists, is there any replace solution for suggestion?

    1. Unfortunately so far I have had little luck with this. At least to have a solution that is clean and robust.
      The only think you could do is wrap your list inside of a helper wrapper class but that always feels so …well …meh for a lack of better words.
      If you really want to give it a go you will need to analyze the path of your property, detect that it is pointing to a sub object of an array and then point to the parent array object for hiding as well. That is what I got working but then just reverted that solution as it was causing other issues for me.
      The array and list issue is something I have learned to live with until perhaps I can find the time to give it another go.
      But besides this, using the attribute saves so much time not having to write custom inspectors for everything 🙂

  11. I’ve been trying to find something exactly like this for a while now and I’m so glad I found this here – I can’t believe it’s not built into Unity! It’s unfortunate this can’t work with lists/arrays but I’ve worked around it.

    Also would be awesome if I could add more than one object to check such as:
    [ConditionalHide(“variable”, check1, check2, check 3, etc)]

    I tried to add an overload method that took another compareObject to ConditionalHideAttribute but I can’t quite figure out how to parse it within the PropertyDrawer script.

    1. Hey Skullflower,

      If you check the latest update of the attribute you can actually provide as many variables as you want using the arrays 😉
      For each entry you can also change if it should be handled as an inverse of the value or not.
      Additionally you can specify if you wish to use AND or OR logic as well.

      Hope this helps.

  12. I was wondering if there is a way to also hide other attributes above the variable to conditionally hide. For instance I have 6 variables in a row with a space attribute between each two. I want to conditionally hide all these 6 variables, but when I do I also want to hide the space attributes, because they leave a gap in my inspector.

    It has to be doable because unitys own [HideInInspector] attribute can do it too? I just cant seem to find out what that exactly does. The hide in inspector attribute also works for Arrays and List, so there has to be a way right?

    Any suggestions on how to handle this?

  13. Just a quick question. I downloaded your files and the property drawer is working, except not on string type variables. I’m not using any complex attributes like the TextAreaAttribute. Seems to work fine with other simple types. Is this correct or have I missed something?

    1. That seems odd. They seem to work just fine on my end on all kinds of types including strings. (tested in 2017,2018 and 2019)

  14. Thanks a bunch for this tutorial. I’ve always wanted to conditionally hide fields in Unity but never had the patience to properly learn how to do it. I’ve looked up a tutorial in the past but I couldn’t make it work (probably missed something). But your guide worked like a charm! I love that you teach how to create an attribute to do this work instead of having a property drawer for each class in which you want to conditionally hide stuff, like I’ve seen in a project I worked on in the past. The attribute makes for an universal solution! Thanks again! Have a nice weekend!

    1. Glad you like it:) That was exactly my problem as well.
      I wanted a more universal, quick to use solution that doesn’t take away time from being able to focus on the important stuff.

  15. Literally spent a few days trying to find something like this, so thank you for sharing!

    When I went through the downloadable scripts and finally understood what they were doing, I did a little refactoring and wound up with something shorter. Hopefully it’ll make the newer code a little easier to understand, so I’ll drop that here. I don’t know how to use code fences here, so my apologies if this looks weird.

    Thanks again!

    Conditional Drawer Script

    “`
    using UnityEditor;
    using UnityEngine;

    // DOES NOT WORK WITH TextAreaAttribute. That property has it’s own show/hide functionality.

    [CustomPropertyDrawer(typeof(ConditionalAttribute))]
    public class ConditionalDrawer : PropertyDrawer
    {
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
    ConditionalAttribute conditional = (ConditionalAttribute)attribute;
    bool enabled = GetConditionalAttributeResult(conditional, property);

    bool wasEnabled = GUI.enabled;
    GUI.enabled = enabled;

    if (enabled) { EditorGUI.PropertyField(position, property, label, true); }
    GUI.enabled = wasEnabled;
    }

    // Calculates the property hieght so there is no visuaul overlap of properties in the inspector.
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
    ConditionalAttribute conditional = (ConditionalAttribute)attribute;
    bool enabled = GetConditionalAttributeResult(conditional, property);

    if (enabled)
    return EditorGUI.GetPropertyHeight(property, label);
    else
    return -EditorGUIUtility.standardVerticalSpacing; // Property is not being drawn. Undo spacing.
    }

    private bool GetConditionalAttributeResult(ConditionalAttribute _conditional, SerializedProperty _property)
    {
    bool enabled = (_conditional.useOrLogic) ? false : true;

    // If the conditon is a string.
    if (_conditional.conditionalSourceField != null)
    {
    SerializedProperty _sourcePropertyValue = null;

    // Handle single poroperty.

    string _propertyPath = _property.propertyPath;
    string _conditionPath = _propertyPath.Replace(_property.name, _conditional.conditionalSourceField);
    _sourcePropertyValue = _property.serializedObject.FindProperty(_conditionPath);

    // If the find failed, hard coded to look for field below.
    if (_property == null)
    _sourcePropertyValue = _property.serializedObject.FindProperty(_conditional.conditionalSourceField);

    if (_sourcePropertyValue != null)
    {
    enabled = CheckPropertyType(_sourcePropertyValue);
    if (_conditional.inverse) enabled = !enabled;
    }
    else
    Debug.LogWarning(“Attempting to use a ConditionalAttribute, but no matching bool variable found in object: ” + _conditional.conditionalSourceField);

    }
    // if the condition is an array of strings.
    else if (_conditional.conditionalSourceFieldArray != null)
    {
    string[] _conditionalSourceFieldArray = _conditional.conditionalSourceFieldArray;
    for (int i = 0; i < _conditionalSourceFieldArray.Length; i++)
    {
    SerializedProperty _sourcePropertyValueFromArray = null;
    if (!_property.isArray)
    {
    string _propertyPath = _property.propertyPath;
    string _conditionPath = _propertyPath.Replace(_property.name, _conditionalSourceFieldArray[i]);
    _sourcePropertyValueFromArray = _property.serializedObject.FindProperty(_conditionPath);

    // If the find failed, use hard code to get field.
    if (_sourcePropertyValueFromArray == null)
    _sourcePropertyValueFromArray = _property.serializedObject.FindProperty(_conditionalSourceFieldArray[i]);
    }
    // Combine results
    if (_sourcePropertyValueFromArray != null)
    {
    bool propertyEnabled = CheckPropertyType(_sourcePropertyValueFromArray);
    Debug.Log(_conditionalSourceFieldArray[i]);
    if (_conditional.inverse) propertyEnabled = !propertyEnabled;

    if (_conditional.useOrLogic) enabled = enabled || propertyEnabled;
    else enabled = enabled && propertyEnabled;
    }
    else
    Debug.LogWarning("Attempting to use a conditional attribute, but no matching bool variable found in object: " + _conditionalSourceFieldArray[i] + ".");
    }
    }

    return enabled;
    }

    private bool CheckPropertyType(SerializedProperty _propertyValue)
    {
    // Add other data types to handel them here.
    SerializedPropertyType _propType = _propertyValue.propertyType;
    if (_propType == SerializedPropertyType.Boolean)
    return _propertyValue.boolValue;
    else if (_propType == SerializedPropertyType.ObjectReference)
    return _propertyValue.objectReferenceValue != null;
    else
    {
    Debug.LogError("Data type of the property used for conditional hiding [" + _propertyValue.propertyType + "] is currently not supported");
    return true;
    }
    }
    }
    “`

    Conditional Attributes Script
    “`
    using UnityEngine;
    using System;

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]
    public class ConditionalAttribute : PropertyAttribute
    {
    public string conditionalSourceField = null;
    public string[] conditionalSourceFieldArray = null;
    public GameObject conditionalGameObject = null;
    public GameObject[] conditionalGameObjectArray = null;
    public bool inverse = false;
    public bool useOrLogic = false;

    public ConditionalAttribute(string _conditionalSourceField)
    {
    this.conditionalSourceField = _conditionalSourceField;
    }

    public ConditionalAttribute(string _conditionalSourceField, bool _inverse)
    {
    this.conditionalSourceField = _conditionalSourceField;
    this.inverse = _inverse;
    }

    public ConditionalAttribute(string _conditionalSourceField, bool _useOrLogic, bool _inverse)
    {
    this.conditionalSourceField = _conditionalSourceField;
    this.useOrLogic = _useOrLogic;
    this.inverse = _inverse;
    }

    public ConditionalAttribute(string[] _conditionalSourceFieldArray)
    {
    this.conditionalSourceFieldArray = new string[] { };
    this.conditionalSourceFieldArray = _conditionalSourceFieldArray;
    }

    public ConditionalAttribute(string[] _conditionalSourceFieldArray, bool _useOrLogic)
    {
    this.conditionalSourceFieldArray = new string[] { };
    this.conditionalSourceFieldArray = _conditionalSourceFieldArray;
    this.useOrLogic = _useOrLogic;
    }

    public ConditionalAttribute(string[] _conditionalSourceFieldArray, bool _useOrLogic, bool _inverse)
    {
    this.conditionalSourceFieldArray = new string[] { };
    this.conditionalSourceFieldArray = _conditionalSourceFieldArray;
    this.useOrLogic = _useOrLogic;
    this.inverse = _inverse;
    }

    public ConditionalAttribute(GameObject _conditionalGameObject)
    {
    this.conditionalGameObject = _conditionalGameObject;
    }

    public ConditionalAttribute(GameObject _conditionalGameObject, bool _useOrLogic)
    {
    this.conditionalGameObject = _conditionalGameObject;
    this.useOrLogic = _useOrLogic;
    }

    public ConditionalAttribute(GameObject _conditionalGameObject, bool _useOrLogic, bool _inverse)
    {
    this.conditionalGameObject = _conditionalGameObject;
    this.useOrLogic = _useOrLogic;
    this.inverse = _inverse;
    }
    }
    “`

    1. Brilliant!
      How would I go on to compare 2 strings and enable the property if they match?

      such as:
      public string1 = “hi”;
      public string2 = “hi”;

      [Conditional(“string1”, “string2”)]
      public bool enabled;

      Appreciate the clear code.

      1. Hey Kamit,

        Just as with the check where you check the specific param to see it’s enabled or false. You can just compare the 2 provided parameters instead with whatever logic you want. (This is done in the drawer)

  16. Hello,
    This is a great feature I love it, I use especially the enum version!
    However I was wondering if it was possible to use the property drawer to conditionally serialize private fields? It bothers me a little to make my variables public because no other class should be able to access them. However when I make them private the serialization becomes impossible, do you have a suggestion?

  17. Hi, a few years down the line – is there now a way for it to play nicely with combining headers or if it’s an array? I’ve had to encapsulate my array in a wrapper class to get array hiding to work.

    1. Sadly no. This has to do with how Unity draws and handles their arrays in the editor. It’s an annoying limitation, I must agree.

    1. Sadly not. Unless Unity changes the way they handle arrays and their editor drawing in general to have more control.

  18. I have the two files inside a Editor folder, but if i try to use ConditionalHide unity complains with:
    The type or namespace name ‘ConditionalHideAttribute’ could not be found (are you missing a using directive or an assembly reference?)
    The type or namespace name ‘ConditionalHide’ could not be found (are you missing a using directive or an assembly reference?)

    I am using Unity 2021.2.5f1

    1. Hey AlbatroZz,

      Makse sure that ConditionalHideAttribute is not inside of an Editor folder and that ConditionalHidePropertyDrawer is inside one. Then it should work just fine 😉

  19. Hello !
    There must be a way for array/list, but it’s not a simple solution and it implies a lot of new classes.

    Look at that project:
    https://github.com/garettbass/UnityExtensions.ArrayDrawer

    They implements custom ArrayDrawer similary to base ListDrawer of Unity.
    I think if you are overwrite that drawer and if you retrieve the CondionalAttribute from the field and resolve the logic of the attribute in the GetPropertyHeight of this ArrayDrawer, you could make it invisible.

    I did not try yet, but if someone is motivated… 😀

    1. If someone wants to spend the time and headaches I would love to see the final version 🙂
      Sadly I’ll pass on this one as well :p

  20. Hello!

    I’ve been using your extension for quite a long time and it has been super useful! Thank you again for it 🙂 I do have an issue with the latest version of unity 2021.3.10f1 (which was working fine with 2021.3.5f1).
    Here is the issue : https://pasteboard.co/GwI3RUUD78Xq.png

    Basically, the values in the array are hidden, but not the array variable itself.

    Any idea why?

    1. Hey Laurel,

      I’m currently not on the latest Unity version yet (our projects are still on 2019 LTS) so I haven’t been able to check this yet.
      If I’m not mistaken Unity changed the way they draw their lists and that is most likely messing with this.
      Are you trying to hide the entire list or is this a list with entries that use the attribute?

      I do know that the attribute never worked with showing/hiding full lists though. Again due to the way Unity handles this.

  21. Yeah, they changed it, it was bugged for specific versions of 2021 but it was fixed again, so I was able to actually use your extension for a long time 🙂 Like I said hiding the full list (like in my example below) was working with 2021.3.5. I was wondering if you had an idea why 🙂

    I am trying to hide the full list.
    For example :
    [ConditionalHide(“isVisible”, true)]
    [SerializeField]
    public List myVisibleStrings = new List();

    Now in the editor I still see the List myVisibleStrings, but I don’t see the entries (like on the picture).

Leave a Reply


The reCAPTCHA verification period has expired. Please reload the page.