Inspector 2 Variables in one Slider

Hey,
today I noticed you can write something like this:

[Range(1,10)]
public float minRangeOfCamera = 2.0f;
[Range(1,10)]
public float maxRangeOfCamera = 20.0f;

in the Inspector there will be a slider to change the values.
But I often saw sliders thich contained 2 slider in it. So there I was able to change the min and the max value in just one slider. Can you tell me how to do it?

Thanks

I don’t think you can without writing your own custom editor, or using a third-party package like the Advanced Inspector.

A min/max range type was added to the next version of the Advanced Inspector; Advanced Inspector - WIP/Feature requests - Community Showcases - Unity Discussions

There is no way without spending money? :confused:

Yes, the first option I gave you… Writing a custom editor/property drawer yourself. It’s really not that hard.

You can use the editor MinMaxSlider for this:

Hi,
I created a JavaScript which contains the code from your first link. But how can I use this Slider in my Inspector now?

You use that in a custom editor, or you could use it to make a custom property drawer.

explains how the [[Range(1,10)] PropertyAttribute is made, and you could base your new code on this.

I have attempted to write a MinMaxSlider property drawer but failed. Is this possible? I can’t seem to get around this error
No appropriate version of ‘UnityEditor.EditorGUILayout.MinMaxSlider’ for the argument list
I really wish a MinMax slider was a simple attribute like Range.

1 Like

Hello here’s what I got just wanna share

It works only with Vector2/Vector2Int!
So the X component represents the from/min and the Y component represents the to/max.
The value to the left of the slider is the X, the value to the right is the Y

These tests all have the same Vector2 values: (x=1, y=2)
It is just the way the MinTo attribute is used that changes what the slider represents in the inspector.
MinTo(): from x to y, between 0 and y.
MinTo(max): from x to y, between 0 and max.
MinTo(max, min): from x to y, between min and max.

I use it for things like a spell min/max distance or like a ranged dude AI distance not too far not too close.
Also for durations, like if you can charge an attack for up to Y second but also release it early after X seconds. So most of the time I just wanna adjust freely the maximum/to and the minimum is always 0 because negative values don’t make sense for these uses, so yea just gives you little fun controls to toy with, also nice way to see how big the range is and where it starts, and also it proofchecks that -you don’t put dumb negative values -your minimum does not exceed the maximum.
I decided to call it MinTo instead of MinMax because most of the time the To is the Max (with zero args) so there’s no Max actually. For example in test1 there’s 2 on the right as the “max” but I can change it to 100000 from the inspector if I want so yea it is more a just a To value than a Maximum by definition.

MinToAttribute.cs: put this script anywhere
MinToAttribute.cs

using UnityEngine;
using System.Collections;

public class MinToAttribute : PropertyAttribute
{
    public float? max;
    public float min;

    public MinToAttribute() {}
    public MinToAttribute(float max, float min = 0)
    {
        this.max = max;
        this.min = min;
    }
}

MinToDrawer.cs: and put this one in an Editor folder!
MinToDrawer.cs

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(MinToAttribute))]
public class MinToDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position,
                               SerializedProperty property,
                               GUIContent label)
    {
        var ctrlRect = EditorGUI.PrefixLabel(position, label);
        Rect[] r = Geom.SplitRectIn3(ctrlRect, 36, 5);
        var att = (MinToAttribute)attribute;
        var type = property.propertyType;
        if (type == SerializedPropertyType.Vector2)
        {
            var vec = property.vector2Value;
            float min = vec.x;
            float to = vec.y;
            min = EditorGUI.FloatField(r[0], min);
            to = EditorGUI.FloatField(r[2], to);
            EditorGUI.MinMaxSlider(r[1], ref min, ref to, att.min, att.max ?? to);
            vec = new Vector2(min < to ? min : to, to);
            property.vector2Value = vec;
        }
        else if (type == SerializedPropertyType.Vector2Int)
        {
            var vec = property.vector2IntValue;
            float min = vec.x;
            float to = vec.y;
            min = EditorGUI.IntField(r[0], (int)min);
            to = EditorGUI.IntField(r[2], (int)to);
            EditorGUI.MinMaxSlider(r[1], ref min, ref to, att.min, att.max ?? to);
            vec = new Vector2Int(Mathf.RoundToInt(min < to ? min : to), Mathf.RoundToInt(to));
            property.vector2IntValue = vec;
        }
        else
            EditorGUI.HelpBox(ctrlRect, "MinTo is for Vector2!!", MessageType.Error);
    }

    public static Rect[] SplitRectIn3(Rect rect, int bordersSize, int space = 0)
    {
        var r = SplitRect(rect, 3);
        int pad = (int)r[0].width - bordersSize;
        int ps = pad + space;
        r[0].width = r[2].width -= ps;
        r[1].width += pad * 2;
        r[1].x -= pad;
        r[2].x += ps;
        return r;
    }
    public static Rect[] SplitRect(Rect a, int n)
    {
        Rect[] r = new Rect[n];
        for (int i = 0; i < n; ++i)
            r[i] = new Rect(a.x + a.width / n * i, a.y, a.width / n, a.height);
        return r;
    }
}
4 Likes

quick version 2 LAST ONE :stuck_out_tongue: of course you can look at the code to see how I did it and to tweak it how you want.

FLOAT
-You can do it with 2 separate float variables now instead of Vector2! That’s cool if you change your mind and just wanna remove the min and just keep the max or the opposite whatever it is less of a commitment then having to use Vector2.
So you just put the thing above the MAX/TO float, then you can let the autoname template get the MIN or specify it. (It is relative to you so if you’re a nested class or struct you don’t have to write “spell.distanceMin” if max is in spell already you just write “distanceMin”)
I didn’t show all the combinations but you can do the same arguments as above for different behaviors.
(oh also I changed signature for test3(3,1) it wasn’t very intuitive now it is written test3(1,3) so it is MinTo(min, max))

NASTY BUG FIXED
-If you selected multiple objects it would override values from one of them on MinTo even if you didn’t do anything, now you can still override for all at once but only if you start tweaking it, just like unity.

*It is using “.NET 4.x Equivalent” if it doesn’t compile that’s most likely because you didn’t enable it you do that in Edit/Project Settings/Player

MinToAttribute.cs

using UnityEngine;
using System.Collections;

// Can use it on:
// VECTOR2 / VECTOR2INT
// FLOAT
// you put the attribute above/before the max value!
// then you specify in the MinTo arguments (if the minName template doesn't work)
// the name of the min float variable
public class MinToAttribute : PropertyAttribute
{
    // $ becomes the name of the max property
    // example: [MinTo] float duration; float durationMin;
    public string minName = "$Min";
    public float? max;
    public float min;

    public MinToAttribute(string minName = null)
    {
        if (minName != null)
            this.minName = minName;
    }
    public MinToAttribute(float max, string minName = null) : this(0, max, minName) { }
    public MinToAttribute(float min, float max, string minName = null) : this(minName)
    {
        this.max = max;
        this.min = min;
    }
}

MinToDrawer.cs (in Editor folder)

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(MinToAttribute))]
public class MinToDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position,
                               SerializedProperty property,
                               GUIContent label)
    {
        position.height = EditorGUIUtility.singleLineHeight;
        var att = (MinToAttribute)attribute;
        var type = property.propertyType;

        string minName = att.minName.Replace("$", property.name);
        int lastDot = property.propertyPath.LastIndexOf('.');
        if (lastDot > -1)
            minName = property.propertyPath.Substring(0, lastDot) + '.' + minName;
        //Debug.Log("minName=" + minName);

        if (type == SerializedPropertyType.Float)
            //label.text = string.Format("({1}-{0})", property.name, minName);
            label.text = " ";
        var ctrlRect = EditorGUI.PrefixLabel(position, label);
        Rect[] r = SplitRectIn3(ctrlRect, 36, 5);
        if (type == SerializedPropertyType.Vector2)
        {
            EditorGUI.BeginChangeCheck();
            var vec = property.vector2Value;
            float min = vec.x;
            float to = vec.y;
            min = EditorGUI.FloatField(r[0], min);
            to = EditorGUI.FloatField(r[2], to);
            EditorGUI.MinMaxSlider(r[1], ref min, ref to, att.min, att.max ?? to);
            vec = new Vector2(min < to ? min : to, to);
            if (EditorGUI.EndChangeCheck())
                property.vector2Value = vec;
        }
        else if (type == SerializedPropertyType.Vector2Int)
        {
            EditorGUI.BeginChangeCheck();
            var vec = property.vector2IntValue;
            float min = vec.x;
            float to = vec.y;
            min = EditorGUI.IntField(r[0], (int)min);
            to = EditorGUI.IntField(r[2], (int)to);
            EditorGUI.MinMaxSlider(r[1], ref min, ref to, att.min, att.max ?? to);
            vec = new Vector2Int(Mathf.RoundToInt(min < to ? min : to), Mathf.RoundToInt(to));
            if (EditorGUI.EndChangeCheck())
                property.vector2IntValue = vec;
        }
        else if (type == SerializedPropertyType.Float)
        {
            EditorGUI.BeginChangeCheck();
            // Line setup
            var line2 = position;
            line2.y += EditorGUIUtility.singleLineHeight;

            // Swap lines
            // Comment these 3 lines if you want the slider above
            // Or uncomment them if you want the slider sandwiched between min and max
            //var y = line2.y;
            //line2.y = r[0].y;
            //r[0].y = r[1].y = r[2].y = y;

            // First we draw the float below/above as normal
            EditorGUI.PropertyField(line2, property);

            // Then the slider
            var minProperty = property.serializedObject.FindProperty(minName);
            if (minProperty?.propertyType != SerializedPropertyType.Float)
            {
                EditorGUI.HelpBox(ctrlRect, "Min float not found!!", MessageType.Info);
                return;
            }
            float minVal = minProperty.floatValue;
            float maxVal = property.floatValue;

            EditorGUI.MinMaxSlider(r[1], ref minVal, ref maxVal, att.min, att.max ?? maxVal);
            EditorGUI.LabelField(r[0], att.min.ToString());

            if (att.max.HasValue && maxVal > att.max.Value)
            {
                // Shows that the max value overflowed the slider
                // So if you just wanna try infinite range and stuff you just put 999
                // and it shows clearly that it is a big test value with the color
                // This is only if you specify a max value in the attribute
                Color c = GUI.contentColor;
                GUI.contentColor = overflowColor;
                EditorGUI.LabelField(r[2], maxVal.ToString());
                GUI.contentColor = c;
            }
            else
                EditorGUI.LabelField(r[2], (att.max ?? maxVal).ToString());

            // Rounding you lose a tiny bit of precision but I don't mind
            // it is just to show 0.84 instead of ugly 0.840041..
            // unless you're in very small values (<0.1) then it doesn't round
            minVal = FRound(minVal);
            maxVal = FRound(maxVal);

            // Proofcheck
            maxVal = Mathf.Max(att.min, maxVal);
            minVal = Mathf.Clamp(minVal, att.min, maxVal);

            // And finally update the variables
            if (EditorGUI.EndChangeCheck())
            {
                minProperty.floatValue = minVal;
                property.floatValue = maxVal;
            }
        }
        else
            EditorGUI.HelpBox(ctrlRect, "MinTo is for Vector2 or float!!", MessageType.Error);
    }

    const float threshold = .1f;
    const float precision = .01f;
    float FRound(float f) => f > threshold ? Mathf.Floor(f / precision) * precision : f;

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        int lines = 1;
        if (property.propertyType == SerializedPropertyType.Float)
            lines = 2;
        return lines * EditorGUIUtility.singleLineHeight;
    }

    // That's orange
    private Color overflowColor = new Color(1f, .55f, .1f, 1);

    public static Rect[] SplitRectIn3(Rect rect, int bordersSize, int space = 0)
    {
        var r = SplitRect(rect, 3);
        int pad = (int)r[0].width - bordersSize;
        int ps = pad + space;
        r[0].width = r[2].width -= ps;
        r[1].width += pad * 2;
        r[1].x -= pad;
        r[2].x += ps;
        return r;
    }
    public static Rect[] SplitRect(Rect a, int n)
    {
        Rect[] r = new Rect[n];
        for (int i = 0; i < n; ++i)
            r[I] = new Rect(a.x + a.width / n * i, a.y, a.width / n, a.height);
        return r;
    }
}
10 Likes

Thank you for this! I came to report a little bug with labels that i see in the new UI (2019.3.0f6). It might be easy fix, but i’m not that good at Editor Drawer scripting! (

I used your above code, and this is the view i get:

(labels scuffed)

Hope this helps and thank you for your work!

Awesome work, I needed to define a Random.Range min and max value in one variable and setting the MinTo property on a Vector2 give me exactly that, where x is the min and y is the max. Not exactly what I wanted but it works really well, thank you. @WoofyWyzpets

+1 Thanks for sharing.

My usage of @WoofyWyzpets variant (Attribute in Scripts/ folder, Drawer in Scripts/Editor folder, fixed the capital-I (eye) letter to be lowercase):

public class SnakeWiggle : MonoBehaviour
{
    // ... cut
    [MinTo(0.0f, 60.0f)]
    [SerializeField] private Vector2 MoveEvery = new Vector2(0.25f, 15.0f);
    // ... cut
}

1 Like

I might be a bit late to the party, but I will add my contribution as well.

I wrote a min/max range attribute that makes dealing with bounded ranges in the Unity inspector much easier and I thought it could help other people too. It supports both Vector2 and Vector2Int and custom floating-point decimal places (0 to 3). It’s available on GitHub and Open UPM. To use it, just add the attribute to a field:

[MinMaxRange(0f, 1f, 3)]
[SerializeField] private Vector2 _normalTemperature = new (0.2f, 0.74f);

And it will be displayed on the inspector using min/max sliders:

Check the repository for more information on features, usage and how to add it to your project.
Repository on GitHub: GitHub - lazysquirrellabs/min_max_range_attribute: A bounded (i.e., with a minimum and maximum) range attribute for Unity's Vector2 and Vector2Int fields.
Package on Open UPM: Min/max range attribute | com.lazysquirrellabs.minmaxrangeattribute | Unity Package (UPM) | OpenUPM

3 Likes