Unity Inspector batching Custom Property Attributes in Arrays

I’ve created a Custom Property Attribute for color variables, which adds a drop down with a quick access of colors we are using a lot as a team. This all works fine.

The issue is if the attribute is added to a color in a struct, and then I have an array of that struct. If I change the value of the color in the array, it affects all of the same parameters of the struct in that array.

In other words, it feels like Unity is batching the Attributes in Arrays. This doesn’t happen with ones with come default with Unity though, such as [Range()], so obviously there is a way to fix it.

Here is my code for the property attribute. (Note: The colors have been changed when i posted this).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

public class DefaultColorAttributes : PropertyAttribute {

    public Color color;
    public DefaultColorAttributes()
    {
        Debug.Log("I made this");
    }

}

public class CustomColors {

    public static Color Cancel = new Color(1, 0, 0, 1); 
    public static Color Focal = new Color(0, 0, 1, 1);
    public static Color Black = new Color(0, 0, 0, 1);
    public static Color Midpoint = new Color(0.5f, 0.5f, 0.5f, 1);
    public static Color White = new Color(1, 1, 1, 1);
    public static Color Select = new Color(0, 1, 0, 1);

    public enum ColorEnum : int { Custom = 0, Focal, Black, Midpoint, White, Cancel, Select }

}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(DefaultColorAttributes))]
public class RangeDrawer : PropertyDrawer {

    private CustomColors .ColorEnum currentEnum = CustomColors .ColorEnum.Custom;

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

        DefaultColorAttributes ColorAttributes = attribute as DefaultColorAttributes;

        Rect enumPosition = position;
        enumPosition.width = position.width / 4;

        Rect colorPosition = position;
        colorPosition.x += position.width / 4;
        colorPosition.width -= position.width / 4;

        currentEnum = (CustomColors .ColorEnum)EditorGUI.EnumPopup(enumPosition, currentEnum);


        switch (currentEnum) {
            case CustomColors .ColorEnum.Custom:
                property.colorValue = EditorGUI.ColorField(colorPosition, property.colorValue);
                break;
            case CustomColors .ColorEnum.Focal:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .Focal);
                break;
            case CustomColors .ColorEnum.Black:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .Black);
                break;
            case CustomColors .ColorEnum.Midpoint:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .Midpoint);
                break;
            case CustomColors .ColorEnum.White:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .White);
                break;
            case CustomColors .ColorEnum.Cancel:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .Cancel);
                break;
            case CustomColors .ColorEnum.Select:
                property.colorValue = EditorGUI.ColorField(colorPosition, CustomColors .Select);
                break;
        }

    }
}
#endif

Here is a script which shows off what I’m talking about

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

    [DefaultColorAttributes()]
    public Color Color1;
    [DefaultColorAttributes()]
    public Color Color2;

    public balhah[] Blahs;

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		
	}
}

[System.Serializable]
public struct balhah {

    [DefaultColorAttributes()]
    public Color BLAH1;
    [DefaultColorAttributes()]
    public Color BLAH2;

    [Range(0,1)] 
    public float range;
}

This is what it looks like 95436-example.png

It’s not super well documented, but yes the idea is that (as an optimization) the same PropertyDrawer instance will be shared in this case. The way I usually recommend working around this if you need it to be stateful is to instead do something like Dictionary<string, ColorEnum> m_CurrentEnumPerPropertyPath instead of simply ColorEnum currentEnum. What you then do is then store values that are keyed with property.propertyPath for use in your EnumPopup.