Логика для ползунков диапазона: min (min, max) работает, но max (min, max) нет

#c# #unity3d #range #clamp

#c# #unity3d #диапазон #зажим

Вопрос:

У меня есть следующий код:

 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);
    }
}
 

В настоящее время он выполняет следующее:

Изменение минимального значения никогда не влияет на максимальное: (желаемое поведение)

введите описание изображения здесь

Изменение максимума влияет на минимум: (нежелательное поведение)

введите описание изображения здесь

Этот очень простой фрагмент кода работает для минимального ползунка, но не для максимального ползунка.

Примечание, пожалуйста, не предлагайте использовать EditorGUI.MinMaxSlider, поскольку он не отображает значения:

введите описание изображения здесь

Комментарии:

1. Я думаю, вам нужно будет отслеживать предыдущие min и max, затем вы можете проверить, какой из них изменился, и выполнить проверку только для этого. В противном случае, когда max равно 1, а min равно 2, вы не знаете, был ли min перемещен выше max, и он должен быть установлен на 1 или max, как перемещенный ниже min, и он должен быть установлен на 2.

2. На самом деле имеет смысл!

3. Ваша первая строка в OnValidate перезаписывает RangeMin , поэтому во второй строке будет использоваться неправильное значение. Сначала присваивайте локальные переменные и только в конце присваивайте полям локальные переменные.

Ответ №1:

Ваша проблема в порядке:

 private void OnValidate() // called at every update in UI to validate/coerce
{
    RangeMin = math.min(RangeMin, RangeMax);
    RangeMax = math.max(RangeMin, RangeMax);
}
 

это «работает» для RangeMin , потому что вы сразу же проверяете и ограничиваете его в тот момент, когда вы его изменили.

Однако при изменении RangeMax вы уже сразу влияете на RangeMin , прежде чем у него появится шанс ограничить RangeMax !

Как и предлагалось, вы должны проверить, какое из двух значений вы в данный момент изменяете, например, как

 [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;
    }
}
 

Другой альтернативой, также уже упомянутой в комментариях, является использование локальных переменных для хранения закрепленных значений, но подождите с присвоением, пока все значения не будут завершены, например,

 private void OnValidate() 
{     
    var newMin = Mathf.Min(RangeMin, RangeMax);        
    var newMax = Mathf.Max(RangeMin, RangeMax);

    RangeMin = newMin;
    RangeMax = newMax;
}
 

В качестве альтернативы вернемся к

Примечание, пожалуйста, не предлагайте использовать EditorGUI.MinMaxSlider, поскольку он не отображает значения.

Я думаю, вы могли бы просто сделать это так, как это уже было сделано непослушными атрибутами

 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;
        }
    }
}
 

И ящик

 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();
        }
    }
}
 

что в итоге выглядит так

 [SerializeField] [MinMaxSlider(0f; 100f)] private float _minMaxSlider;
 

введите описание изображения здесь

Теперь, прежде чем копировать этот код, обратите внимание, что пакет Naughty Attributes доступен в хранилище ресурсов Unity бесплатно и содержит гораздо больше приятных улучшений для редактора ( ReorderableList , Button , ShowIf , и т.д.) 😉