Hide array field in custom inspector script

I’m writing a custom inspector script for my Vendor Items, all is going well but I can’t figure out how to hide certain fields if not needed. If I select “Unlimited” I would like to hide the “Quantity” field same for “Requirements” (Class & Level) it there are none. Thanks in advance.

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Vendor))]
public class Vendor_Inspector : Editor
{
    private SerializedProperty vendorItems;

    private void OnEnable()
    {
        vendorItems = serializedObject.FindProperty("items");
    }

    public override void OnInspectorGUI()
    {
        Vendor vendor = (Vendor)target;

        GUIStyle labelStyle = new(GUI.skin.label)
        {
            fontStyle = FontStyle.BoldAndItalic,
            richText = true,
            alignment = TextAnchor.MiddleCenter,
        };

        serializedObject.Update();
        vendorItems.isExpanded = EditorGUILayout.Foldout(vendorItems.isExpanded, vendorItems.name);

        if (vendorItems.isExpanded)
        {
            EditorGUI.indentLevel++;

            // item count label
            if (vendorItems.arraySize > 1)
            {
                EditorGUILayout.LabelField("<color=#baddff>Vendor has </color>" + vendorItems.arraySize + "<color=#baddff> Items For Sale</color>", labelStyle);
            }
            else
            {
                EditorGUILayout.LabelField("<color=#baddff>Vendor has </color>" + vendorItems.arraySize + "<color=#baddff> Item For Sale</color>", labelStyle);
            }

            // draw item fields
            for (var i = 0; i < vendorItems.arraySize; i++)
            {
                var item = vendorItems.GetArrayElementAtIndex(i);
                string name = vendor.MyItems[i].ItemForSale.ToString();

                // ----->
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.PropertyField(item, new GUIContent($"Item: {name}"));

                if (vendor.MyItems[i].Unlimited == false)
                {
                    // hide Quantity field
                }
                if (vendor.MyItems[i].Requirements == true)
                {
                    // hide ItemClass field
                    // hide ItemLevel field
                }

                Color originalColor = GUI.backgroundColor;
                GUI.backgroundColor = Color.red;

                if (GUILayout.Button("Remove", GUILayout.Width(60)))
                {
                    vendorItems.DeleteArrayElementAtIndex(i);
                }

                GUI.backgroundColor = originalColor;
                EditorGUILayout.EndHorizontal();
                // <-----
            }

            GUILayout.Space(20);

            // button for adding more items
            if (GUILayout.Button("Add New Vendor Item"))
            {
                vendorItems.InsertArrayElementAtIndex(vendorItems.arraySize);
            }

            EditorGUI.indentLevel--;

            // apply changes to the serializedProperty
            if (GUI.changed)
            {
                EditorUtility.SetDirty(target);
            }
        }

        serializedObject.ApplyModifiedProperties();
    }
}

This wouldn’t be done at the the Editor level. You would have to write a PropertyDrawer for whatever the type is in this array, and handle the drawing or hiding of fields in there.

The array of items is in a VendorItem.cs that does not inherit from MonoBehaviour but is marked with [System.Serializable], do I make a PropertyDrawer for that Class and handle the drawing or hiding of fields in there.

If so can you point me to something that would explain how to do it?

Yes you would write a property drawer for VendorItem.

Plenty of examples in the docs: Unity - Scripting API: PropertyDrawer

If the docs haven’t been updated since I reported it, property drawers are recycled, so they need to be stateless.

1 Like

I followed spiney199’s suggestion and got the functionality that I was looking for but now I’ve ran into another problem. The drawer doesn’t seam to be placed correctly in the array element and I’ve lost control of the expand element. Any help or code would be appreciated as I’m just learning editor scripts, this is my 1st one :slight_smile: I’ll show the PropertyDrawer script if that helps.

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(VendorItem))]
public class VendorItemDrawer : PropertyDrawer
{
    // Draw the property inside the given rect
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Using BeginProperty / EndProperty on the parent property means that
        // prefab override logic works on the entire property.
        EditorGUI.BeginProperty(position, label, property);

        // Draw label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // Calculate rects
        var itemForSale = new Rect(position.x, position.y, 200, position.height);
        var unlimited = new Rect(position.x + 205, position.y, 25, position.height);
        var quantity = new Rect(position.x + 225, position.y, 30, position.height);

        var requirements = new Rect(position.x, position.y + 30, 20, position.height);
        var itemClass = new Rect(position.x + 20, position.y + 30, 200, position.height);
        var itemLevel = new Rect(position.x + 225, position.y + 30, 30, position.height);

        // Draw fields - pass GUIContent.none to each so they are drawn without labels
        EditorGUI.PropertyField(itemForSale, property.FindPropertyRelative("itemForSale"), GUIContent.none);
        EditorGUI.PropertyField(unlimited, property.FindPropertyRelative("unlimited"), GUIContent.none);
        if (property.FindPropertyRelative("unlimited").boolValue == false)
        {
            EditorGUI.PropertyField(quantity, property.FindPropertyRelative("quantity"), GUIContent.none);
        }

        EditorGUI.PropertyField(requirements, property.FindPropertyRelative("requirements"), GUIContent.none);
        if (property.FindPropertyRelative("requirements").boolValue == true)
        {
            EditorGUI.PropertyField(itemClass, property.FindPropertyRelative("itemClass"), GUIContent.none);
            EditorGUI.PropertyField(itemLevel, property.FindPropertyRelative("itemLevel"), GUIContent.none);
        }

        // Set indent back to what it was
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

For IMGUI property drawers, you will need to also override GetPropertyHeight and calculate the correct height: Unity - Scripting API: PropertyDrawer.GetPropertyHeight

If its just a bunch of normal fields one after another, you can simply multiply this for however many fields you will be drawing: Unity - Scripting API: EditorGUIUtility.singleLineHeight

1 Like

Thanks for the advice you’ve given me so far but could you help me with this part, I don’t really know where to put this and the syntax that I should use.

As I said this is new to me and I’ve never dabbled with Property Drawers before, and yes the fields just need to be one after another. I learn better with example code.

This is what I’ve got so far, just need to know how to add the fields now.

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(VendorItem))]
public class VendorItemDrawer : PropertyDrawer
{
    private const float FOLDOUT_HIGHT = 16f;

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        float height = FOLDOUT_HIGHT;

        if (property.isExpanded)
        {
            height = 6 * FOLDOUT_HIGHT;
        }

        return height;
    }

    // Draw the property inside the given rect
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property); // ----->
        Rect foldoutRect = new Rect(position.x, position.y, position.width, FOLDOUT_HIGHT);
        property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);

        if (property.isExpanded)
        {
        }

        EditorGUI.EndProperty(); // <-----
    }
}

I mean how you were drawing them before was probably fine. You just had to tell the property drawer how tall to be.

Nearly there, If someone could be kind enough to show me the code needed to hide the “Quantity” field(red area) when “Unlimited” is set to true, I’ve managed to deal with the “Requirements” as that’s at the bottom of the drawer. I’ll show the full code for the Property Drawer if you could fill in the missing code. Thanks in advance.

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(VendorItem))]
public class VendorItemDrawer : PropertyDrawer
{
    private const float FOLDOUT_HIGHT = 16f;

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        float height = FOLDOUT_HIGHT;

        if (property.isExpanded)
        {
            if (property.FindPropertyRelative("requirements").boolValue == false)
            {
                height = 4 * FOLDOUT_HIGHT + 50;
            }
            else
            {
                height = 6 * FOLDOUT_HIGHT + 60;
            }
        }

        return height;
    }

    // Draw the property inside the given rect
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property); // ----->
        Rect foldoutRect = new(position.x, position.y, position.width, FOLDOUT_HIGHT);
        property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);

        if (property.isExpanded)
        {
            // Don't make child fields be indented
            var indent = EditorGUI.indentLevel;
            EditorGUI.indentLevel = 2;

            // Calculate Rect's
            var itemForSale = new Rect(foldoutRect.position.x, foldoutRect.position.y + 20, foldoutRect.width, FOLDOUT_HIGHT);
            var unlimited = new Rect(foldoutRect.position.x, foldoutRect.position.y + 40, foldoutRect.width, FOLDOUT_HIGHT);
            var quantity = new Rect(foldoutRect.position.x, foldoutRect.position.y + 60, foldoutRect.width, FOLDOUT_HIGHT);
            var requirements = new Rect(foldoutRect.position.x, foldoutRect.position.y + 80, foldoutRect.width, FOLDOUT_HIGHT);
            var itemClass = new Rect(foldoutRect.position.x, foldoutRect.position.y + 100, foldoutRect.width, FOLDOUT_HIGHT);
            var itemLevel = new Rect(foldoutRect.position.x, foldoutRect.position.y + 120, foldoutRect.width, FOLDOUT_HIGHT);

            EditorGUI.PropertyField(itemForSale, property.FindPropertyRelative("itemForSale"), new GUIContent("Item For Sale"));
            EditorGUI.PropertyField(unlimited, property.FindPropertyRelative("unlimited"), new GUIContent("Unlimited"));

            if (property.FindPropertyRelative("unlimited").boolValue == false)
            {
                EditorGUI.PropertyField(quantity, property.FindPropertyRelative("quantity"), new GUIContent("Quantity"));
            }
            else
            {
                // hide quantity field

                // set height - ??
            }

            EditorGUI.PropertyField(requirements, property.FindPropertyRelative("requirements"), new GUIContent("Requirements"));
            if (property.FindPropertyRelative("requirements").boolValue == true)
            {
                EditorGUI.PropertyField(itemClass, property.FindPropertyRelative("itemClass"), new GUIContent("Item Class"));
                EditorGUI.PropertyField(itemLevel, property.FindPropertyRelative("itemLevel"), new GUIContent("Item Level"));
            }

            // Set indent back to what it was
            EditorGUI.indentLevel = indent;
        }

        EditorGUI.EndProperty(); // <-----
    }
}

Just do the same thing you’re already doing with the requirements field: calculate a smaller height.

But also don’t include it in the rects you generate. Though rather than instancing all these rects, I would start off with just the one rect and just recycle that, shifting it downwards with each field, so that if you skip a field, you don’t get a gap.

It might be easier to make a helper function here:

public static class RectExtensions
{
    public static Rect MoveY(this Rect rect, float yOffset)
    {
        Vector2 position = rect.position;
        position.y += yOffset;
        return new Rect(position, rect.size)
    }
}

Then just reuse a rect for each control:

Rect controlRect = foldoutRect.MoveY(FOLDOUT_HIGHT);
EditorGUI.PropertyField(controlRect, property.FindPropertyRelative("itemForSale"), new GUIContent("Item For Sale"));
 
controlRect = controlRect.MoveY(FOLDOUT_HIGHT);
EditorGUI.PropertyField(controlRect, property.FindPropertyRelative("unlimited"), new GUIContent("Unlimited"));

if (property.FindPropertyRelative("unlimited").boolValue == false)
{
    controlRect = controlRect.MoveY(FOLDOUT_HIGHT);
    EditorGUI.PropertyField(controlRect, property.FindPropertyRelative("quantity"), new GUIContent("Quantity"));
}
1 Like

Thanks very much, I’ll mark your post as the solution and give it a like. I think I should have enough information to finish it off.

1 Like

I’d like to end this thread with the files incase someone comes here looking for a similar thing, I know it’s not a complete thing, the “Vendor” and “VendorItem” scripts are unique to my game but I hope the code helps someone. Here’s a screenshot of the final inspector with all 4 possibility’s shown and again, a big thanks to @spiney199 for all the help and code.

Scripts:
VendorItem_Inspector.cs (2.7 KB)
VendorItemDrawer.cs (3.6 KB)
RectExtensions.cs (268 Bytes)

1 Like