Can't change values for CustomProperty from Inspector

Hi everyone,

I’ve written a little custom class for some utility regarding numeric settings (e.g. minimum and maximum map size, and alike).

[System.Serializable]
public abstract class ClampedValue<T>
{
    [field: UnityEngine.SerializeField] public T Min { get; protected set; }
    [field: UnityEngine.SerializeField] public T Max { get; protected set; }

    [UnityEngine.SerializeField] protected T val;
    public abstract T Value { get; set; }

    public ClampedValue(T value, T min, T max)
    {
        Min = min;
        Max = max;
        Value = value;
    }
}

[System.Serializable]
public class ClampedInt : ClampedValue<int>
{
    public override int Value
    {
        get => val;
        set
        {
            val = Mathf.Clamp(value, Min, Max);
        }
    }

    public ClampedInt() : this(default(int), int.MinValue, int.MaxValue) { }
    public ClampedInt(int value, int min, int max) : base(value, min, max) { }
}

I also want to add a custom Property Drawer to that to make displaying them in the Inspector a little less verbose.


[CustomPropertyDrawer(typeof(ClampedInt))]
public class ClampedIntDrawer : PropertyDrawer
{
    private static readonly int controlHash = "Foldout".GetHashCode();

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        ClampedInt target = (ClampedInt)property.boxedValue;
        label = EditorGUI.BeginProperty(position, label, property);
        int indent = EditorGUI.indentLevel;

        int id = GUIUtility.GetControlID(controlHash, FocusType.Keyboard, position);
        Rect prefix = EditorGUI.PrefixLabel(position, id, label);
        Rect sliderPos = prefix;

        EditorGUI.indentLevel = 0;
        target.Value = EditorGUI.IntSlider(sliderPos, target.Value, target.Min, target.Max);
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

That should give me a slider that is constrained from a ClampedInt’s Min value to its Max value, allowing me to slide up and down to change its current value. And although the slider is displayed correctly (i.e., it is shown, the initial value is correct and the handle is placed correctly in regards to its min and max values), I can’t interact with it.
With Debug.Log() I already checked that the Slider does register the value change but it is instantly reverted. It applies the new value to the ClampedInt field and is then instantly reverted to the original value and I don’t know why. Can anybody help me with that?

Thank you in advance!

When modifying serialized properties, you should do it through the SerializedProperty instead of the boxedValue.

The “boxedValue” might even give you the propper instance that is currently assigned on your Component, but as you are not notifying the serialization system that you have modified it, it will neither serialize it, nor make the propper “Undo” entries. Also, when an UnityEngine.Object is being inspected, it is being deserialized every time the Inspector is drawn (mostly once or more per frame) . So your new value gets overwritten with the unchanged, serialized one.

You should instead write to the "val" sub-property. And ideally, only do it if the value has actually really changed

// ....
using (var serializedVal = property.FindPropertyRelative("val"))
using (var change = new EditorGUI.ChangeCheckScope())
{
    int oldValue = target.Value;
    int newValue = EditorGUI.IntSlider(sliderPos, oldValue, target.Min, target.Max);
    if (change.changed && oldValue != newValue)
    {
        serializedVal.intValue = newValue;
    }
}
// ...
1 Like

Actually, I tried that approach before and for whatever reason it didn’t work out for me. That’s why I resorted to using boxedValue in the first place. Now that I’m going through it for a second time, I got it to work. Seems like I missed something initially. Now it works just as intended - thank you for pointing me in the right direction.

1 Like