Creating Two Sliders With PropertyDrawer

I’m currently writing a Custom RangeAttribute for Vector2 and want the X and Y Value each to be adjustable by a slider in the inspector.
However the second slider doesn’t seem to be working, i.e. when I try dragging the second slider, the value of the first slider will be changed instead.

[CustomPropertyDrawer(typeof(Vector2RangeAttribute))]
public class Vector2RangeAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var slider2Pos = new Rect(position);
        slider2Pos.position += new Vector2(0, 25);

        var rangeAttribute = (Vector2RangeAttribute)attribute;

        var valX = property.vector2Value.x;
        var valY = property.vector2Value.y;

        valX = EditorGUI.Slider(position, valX, rangeAttribute.minX, rangeAttribute.maxX);
        valY = EditorGUI.Slider(slider2Pos, valY, rangeAttribute.minY, rangeAttribute.maxY);

        property.vector2Value = new Vector2(valX, valY);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return 40f;
    }
}

Here is my Vector2RangeAttribute Class, for which I want to create the Custom Property Drawer:

public class Vector2RangeAttribute : PropertyAttribute
{
    public readonly float minX;
    public readonly float maxX;

    public readonly float minY;
    public readonly float maxY;

    public Vector2RangeAttribute(float fMinX, float fMaxX, float fMinY, float fMaxY)
    {
        minX = fMinX;
        maxX = fMaxX;

        minY = fMinY;
        maxY = fMaxY;
    }
}

Anyone know how to create multiple sliders for one Property? Or is that not possible?
I tested with only one slider for the X value and that worked fine so I’m really stumped at what’s wrong.

Edit: Scratch that, I’m stupid. I forgot to set the Height of the position Rect to be smaller than the whole property drawer, so of course I couldn’t control the second slider :sweat_smile:.

Just had to put position.height = 20; at the start of OnGUI.

Found it 5 minutes after posting this, but couldn’t find it in an hour of trying before :rage:.

Right. To answer this question in a more general way: With overriding “GetPropertyHeight” you will specify the total height that your property drawer needs / wants. When the OnGUI method is called, Unity will actually pass a rect with that height. It’s up to you to split up this Rect in any way you like to design your layout. In your case you specifed a total height of 40 and simply set the Rect’s height to 20 (a single line) which is used for both sliders but the y position is updated for the second slider.

On that note: Watch out. Currently you reserve 40 pixels in height and you set a single line to a height of 20. However you offset the second line by 25 pixels. That makes your total property 45 pixels in height. So you may overlap 5 pixels with the following property. Make sure your space is setup correctly. It’s common to use something like this for GetPropertyHeight:

return 20f * 2 + 5;

Though using actual constants or variables would be more descriptive

return lineHeight * 2 + space;

Note that there is EditorGUIUtility.singleLineHeight which probably should be used instead of that “20”. It actually has a value of “18”. See here and here.

The vertical spacing is mostly controlled by the internal “kVerticalSpacingMultiField” value which is a theme dependent value. Though spacing is something that isn’t that much of an issue. Anyways, when you use some spacing, make sure you include it in your height calculation.

The same may apply when you want to split the space horizontally. Here it’s common to simply divide the available space by 2 (or 3 or whatever needed) and simply set the x of the next Rect to the xMax of the previous Rect. Though when you want to use spacing here as well, you have to factor that in as well. If you do a lot of such editor stuff, you may want to create some helper methods to split up the available space. For example there is the built-in EditorGUI.PrefixLabel method which takes the total horizonzal Rect as input, draws a label and returns the offset and reduced Rect of the remaining space after the label that may be used by the “value” control.

Just keep in mind whatever you do with your available space to stay inside that space. Otherwise you can get unwanted overlaps with other elements.

ps:
This code:

        var valX = property.vector2Value.x;
        var valY = property.vector2Value.y;
        valX = EditorGUI.Slider(position, valX, rangeAttribute.minX, rangeAttribute.maxX);
        valY = EditorGUI.Slider(slider2Pos, valY, rangeAttribute.minY, rangeAttribute.maxY);
        property.vector2Value = new Vector2(valX, valY);

would be better like this:

        var val = property.vector2Value;
        val.x = EditorGUI.Slider(position, val.x, rangeAttribute.minX, rangeAttribute.maxX);
        val.y = EditorGUI.Slider(slider2Pos, val.y, rangeAttribute.minY, rangeAttribute.maxY);
        property.vector2Value = val;

What way the property is only read once. Just a tiny improvement but such multiple vector property readings kinda bother me when only one element is read each time ^^. It means the whole vector is copied each time and only one value is used in each case. The access to val.x is actually a direct access of a local variable in the method with no real overhead.