I have the following code:
public class Test : UnityEngine.MonoBehaviour
{
[Range(0.0f, 1.0f)] // draws a slider restricted to 0.0 <-> 1.0 range in UI
public float RangeMin = 0.0f;
[Range(0.0f, 1.0f)] // draws a slider restricted to 0.0 <-> 1.0 range in UI
public float RangeMax = 1.0f;
private void OnValidate() // called at every update in UI to validate/coerce
{
RangeMin = math.min(RangeMin, RangeMax);
RangeMax = math.max(RangeMin, RangeMax);
}
}
Currently, it does the following:
Changing minimum does never influence maximum: (desired behavior)
Changing maximum does influence minimum: (unwanted behavior)
That very simple piece of code works for minimum slider but not for maximum slider.
Note, please don't suggest using EditorGUI.MinMaxSlider as it doesn't show the values:
Your problem is the order:
private void OnValidate() // called at every update in UI to validate/coerce
{
RangeMin = math.min(RangeMin, RangeMax);
RangeMax = math.max(RangeMin, RangeMax);
}
it "works" for the RangeMin
because you immediately check and limit it in the moment you changed it.
However, while changing RangeMax
you already influence immediately the RangeMin
, before it gets a chance to limit the RangeMax
!
As suggested you should check which of the two values you are currently changing e.g. like
[HideInInspector] private float lastMin;
[HideInInspector] private float lastMax;
private void OnValidate()
{
if(!Mathf.Approximately(lastMin, RangeMin))
{
RangeMin = Mathf.Min(RangeMin, RangeMax);
lastMin = RangeMin;
}
if(!Mathf.Approximately(lastMax, RangeMax))
{
RangeMax = Mathf.Max(RangeMin, RangeMax);
lastMax = RangeMax;
}
}
Another alternative also already mentioned in the comments is using local variables to store the clamped values but wait with the assignment until all values are finished clamping like e.g.
private void OnValidate()
{
var newMin = Mathf.Min(RangeMin, RangeMax);
var newMax = Mathf.Max(RangeMin, RangeMax);
RangeMin = newMin;
RangeMax = newMax;
}
Alternatively back to the
Note, please don't suggest using EditorGUI.MinMaxSlider as it doesn't show the values.
I guess you could simply make it like it was already done by Naughty Attributes
namespace NaughtyAttributes
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class MinMaxSliderAttribute : DrawerAttribute
{
public float MinValue { get; private set; }
public float MaxValue { get; private set; }
public MinMaxSliderAttribute(float minValue, float maxValue)
{
MinValue = minValue;
MaxValue = maxValue;
}
}
}
And the drawer
namespace NaughtyAttributes.Editor
{
[CustomPropertyDrawer(typeof(MinMaxSliderAttribute))]
public class MinMaxSliderPropertyDrawer : PropertyDrawerBase
{
protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
{
return (property.propertyType == SerializedPropertyType.Vector2)
? GetPropertyHeight(property)
: GetPropertyHeight(property) + GetHelpBoxHeight();
}
protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
MinMaxSliderAttribute minMaxSliderAttribute = (MinMaxSliderAttribute)attribute;
if (property.propertyType == SerializedPropertyType.Vector2)
{
EditorGUI.BeginProperty(rect, label, property);
float indentLength = NaughtyEditorGUI.GetIndentLength(rect);
float labelWidth = EditorGUIUtility.labelWidth + NaughtyEditorGUI.HorizontalSpacing;
float floatFieldWidth = EditorGUIUtility.fieldWidth;
float sliderWidth = rect.width - labelWidth - 2.0f * floatFieldWidth;
float sliderPadding = 5.0f;
Rect labelRect = new Rect(
rect.x,
rect.y,
labelWidth,
rect.height);
Rect sliderRect = new Rect(
rect.x + labelWidth + floatFieldWidth + sliderPadding - indentLength,
rect.y,
sliderWidth - 2.0f * sliderPadding + indentLength,
rect.height);
Rect minFloatFieldRect = new Rect(
rect.x + labelWidth - indentLength,
rect.y,
floatFieldWidth + indentLength,
rect.height);
Rect maxFloatFieldRect = new Rect(
rect.x + labelWidth + floatFieldWidth + sliderWidth - indentLength,
rect.y,
floatFieldWidth + indentLength,
rect.height);
// Draw the label
EditorGUI.LabelField(labelRect, label.text);
// Draw the slider
EditorGUI.BeginChangeCheck();
Vector2 sliderValue = property.vector2Value;
EditorGUI.MinMaxSlider(sliderRect, ref sliderValue.x, ref sliderValue.y, minMaxSliderAttribute.MinValue, minMaxSliderAttribute.MaxValue);
sliderValue.x = EditorGUI.FloatField(minFloatFieldRect, sliderValue.x);
sliderValue.x = Mathf.Clamp(sliderValue.x, minMaxSliderAttribute.MinValue, Mathf.Min(minMaxSliderAttribute.MaxValue, sliderValue.y));
sliderValue.y = EditorGUI.FloatField(maxFloatFieldRect, sliderValue.y);
sliderValue.y = Mathf.Clamp(sliderValue.y, Mathf.Max(minMaxSliderAttribute.MinValue, sliderValue.x), minMaxSliderAttribute.MaxValue);
if (EditorGUI.EndChangeCheck())
{
property.vector2Value = sliderValue;
}
EditorGUI.EndProperty();
}
else
{
string message = minMaxSliderAttribute.GetType().Name + " can be used only on Vector2 fields";
DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning);
}
EditorGUI.EndProperty();
}
}
}
which in the end looks like
[SerializeField] [MinMaxSlider(0f; 100f)] private float _minMaxSlider;
Now before copying that code, note that Naughty Attributes Package is available in the Unity Asset Store for free and has a lot more nice enhancements for the editor (ReorderableList
, Button
, ShowIf
, etc) ;)