В своей предыдущей статье я обещал рассказать, свой способ работы с прямоугольниками. Разрабатывая OneLine, я написал несколько расширений класса Rect, заметно упрощающих работу с GUI. Сейчас я выделил их в отдельную библиотеку: RectEx.
Подробности под катом.
Суть проблемы
Когда мы пишем PropertyDrawer в Unity, мы вынуждены пользоваться классом GUI (вместо GUILayout), а значит работать с разметкой руками. Код обрастает множеством new Rect(...)
и rect.y += rect.height + 5
, усложняется для чтения и изменений. Когда в дело замешиваются магические числа (далее будут примеры с просторов интернета), код становится настолько инертным, что каждое новое изменение воспринимается программистом как издевательство со стороны геймдизайнера.
Долгое время я мирился с проблемой, просто пытаясь не делать слишком плохих вещей. Но когда занялся разработкой OneLine, параллельно написал и ряд расширений для класса Rect, упрощающих рутинную работу.
Как это делают люди
На просторах интернета я нашел множество способов нарезать прямоугольники в туториалах и исходниках на гитхабе. Далее идет небольшая подборка. Найдете ли Вы среди них свой любимый? Если нет, напишите в комментариях свой вариант, я добавлю в статью.
Найденные примеры я правил по своему усмотрению, чтобы убрать все лишнее и сделать их нагляднее.
Готовим прямоугольники заранее
// Calculate rects
var amountRect = new Rect (position.x, position.y, 30, position.height);
var unitRect = new Rect (position.x+35, position.y, 50, position.height);
var nameRect = new Rect (position.x+90, position.y, position.width-90, position.height);
// Draw fields - passs GUIContent.none to each so they are drawn without labels
EditorGUI.PropertyField (amountRect, property.FindPropertyRelative ("amount"), GUIContent.none);
EditorGUI.PropertyField (unitRect, property.FindPropertyRelative ("unit"), GUIContent.none);
EditorGUI.PropertyField (nameRect, property.FindPropertyRelative ("name"), GUIContent.none);
Источник здесь.
Rect minRect = new Rect(position.x,
position.y,
position.width * 0.4f - 5,
position.height);
Rect mirroredRect = new Rect(position.x + position.width *
position.y,
position.width * 0.2f,
position.height);
Rect maxRect = new Rect(position.x + position.width * 0.6f + 5,
position.y,
position.width * 0.4f - 5,
position.height);
Источник здесь.
var firstRect = new Rect(position){
width = position.width / 2
};
var secondRect = new Rect(position){
x = position.x + position.width / 2,
width = position.width / 2
};
EditorGUI.PropertyField(firstRect, property.FindPropertyRelative("first"));
EditorGUI.PropertyField(secondRect, property.FindPropertyRelative("second"));
Источник здесь.
Ладно, туториалы хороши тогда, когда учат делать что-то одно, а не содержат все лучшие практики. Конкретно эти учат понакидать побольше магических чисел.
float curveWidth = 50;
var sliderRect = new Rect (rect.x, rect.y, rect.width - curveWidth, rect.height)
EditorGUI.Slider (sliderRect, scale, min, max, label);
var curveRect = new Rect (rect.width - curveWidth, rect.y, curveWidth, rect.height);
EditorGUI.PropertyField (curveRect, curve, GUIContent.none);
Источник здесь.
Такой код тяжело читать в случаях, когда рисуется большое количество свойств.
Такой код тяжело поддерживать. Даже если мы рисуем три свойства и вдруг нужно добавить четвертое/пятое.
Однако есть способ лучше!
Один прямоугольник: нарисовал => подвинул
float count = labels.Length;
float space = 2;
float width = (position.width - (count - 1) * space) / count;
position.width = num2;
for (int i = 0; i < count; i++){
EditorGUI.PropertyField(position, properties[i], labels[i]);
position.x += count + space;
}
Источник здесь
public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label) {
Rect position = rect;
float height = EditorGUIUtility.singleLineHeight;
float space = EditorGUIUtility.standardVerticalSpacing;
position.height = height;
var property = prop.FindPropertyRelative("m_NormalColor");
var property2 = prop.FindPropertyRelative("m_HighlightedColor");
var property3 = prop.FindPropertyRelative("m_PressedColor");
var property4 = prop.FindPropertyRelative("m_DisabledColor");
var property5 = prop.FindPropertyRelative("m_ColorMultiplier");
var property6 = prop.FindPropertyRelative("m_FadeDuration");
EditorGUI.PropertyField(position, property);
position.y += height + space;
EditorGUI.PropertyField(position, property2);
position.y += height + space;
EditorGUI.PropertyField(position, property3);
position.y += height + space;
EditorGUI.PropertyField(position, property4);
position.y += height + space;
EditorGUI.PropertyField(position, property5);
position.y += height + space;
EditorGUI.PropertyField(position, property6);
}
Источник здесь.
В этот раз код читается значительно лучше, и поддерживать его будет несколько проще. Однако все выглядит так хорошо только пока все поля класса имеют одинаковый размер на экране (в первом примере на равные части делится ширина, во-втором примере — высота).
Этот код значительно усложняется, если необходимо рисовать элементы разного размера.
Как это делают с RectEx
RectEx добавляет несколько методов, расширяющих класс Rect, но наиболее полезны два: Column и Row.
Почему такие странные названия?
Сначала я назвал их SplitVertically и SplitHorizontally. Оказалось слишком длинно, неудобно, да еще и не читалось.
Я попробовал SplitV и SplitH. Получилось короче и удобней. Однако, постоянно забываешь, что же каждый из них делает? Один режет горизонтальными линиями, другой — вертикальными. Или один возвращает горизонтальный столбец, другой — вертикальный?
На помощь как всегда пришла математика, а точнее господа Вектор-Столбец и Вектор-Строка (оба слова с большой, потому как господ фамилии двойные). Уж глядя на rect.Row(5)
сразу понимаешь, что метод возвращает строку, а rect.Column(5)
— столбец.
Дальше идут демонстрации.
var rects = rect.Row(3);
EditorGUI.PropertyField(rects[0], property.FindPropertyRelative("first"));
EditorGUI.PropertyField(rects[1], property.FindPropertyRelative("second"));
EditorGUI.PropertyField(rects[2], property.FindPropertyRelative("third"));
Я добавил i++
, чтобы было проще менять строки местами.
var rects = rect.Column(3);
int i = 0;
EditorGUI.PropertyField(rects[i++], property.FindPropertyRelative("first"));
EditorGUI.PropertyField(rects[i++], property.FindPropertyRelative("second"));
EditorGUI.PropertyField(rects[i++], property.FindPropertyRelative("third"));
В этом примере мы передаем методу Column относительные веса, на основе которых получим: второй элемент в два раза больше первого, а третий — в три.
var rects = rect.Column(new float[]{1, 2, 3});
EditorGUI.PropertyField(rects[0], property.FindPropertyRelative("first"));
EditorGUI.PropertyField(rects[1], property.FindPropertyRelative("second"));
EditorGUI.PropertyField(rects[2], property.FindPropertyRelative("third"));
Для наглядности я нарисовал две симметричные картинки, на которых попытался показать пример использования методов Raw и Column (картинки кликабельные).
Где взять?
Текущая версия: v0.1.0.
Попробовать можно на гитхабе. В ридми описаны остальные методы.
Комментарии (4)
scab
10.11.2017 08:01Есть же GuiLayout.BeginArea(rect)/EndArea и поехали верстать гуй через GuiLayout. Разве нет?
SlavniyTeo Автор
10.11.2017 08:13Иногда нет.
Например, когда пишешь PropertyDrawer. В этом случае методы GUILayout не работают вообще.
Или когда необходимо рисовать элементы поверх друг друга. Думаю, этого можно добиться используя GUILayoutUtility.GetLastRect + GUILayout.BeginArea/EndArea. Но мне жаль людей, которые будут поддерживать этот код.
FadeToBlack
«Плюс» за глубину и подход исследователя в решении даже простых проблем.