Добавлен Devion,
опубликован
Последние дни очень много кропчу над редактором. И естественно, какой редактор под юнькой без стандартного GUI? Да, по сути - большая часть работы - это сказка о том как сделать так, чтобы стандартный GUI работал как мне нужно.
А пишу я сейчас иерархию, точнее инструмент для ее создания. Да, долго, но это стандартный GUI, и чтобы сделать эту работу качественно - нужно изрядно попотеть и написать с десяток костылей. Вообще, можно сказать - это нативный GUI и обычно дальше объяснять про сроки не приходится.
Как я ошибку в GUI нашел
Позавчера, надо сказать, я наконец-то научился избавляться от ошибки, которая ранее часто меня бесила. Не то чтобы она мешала, именно бесила - выскакивает время от времени при рисовании.
А содержание ошибки следующее:
А содержание ошибки следующее:
ArgumentException: Getting control 0's position in a group with only 0 controls when doing Repaint
Цифра - произвольная, событие - тоже. И указывает туда, где как оказалось собака зарыта не была. Потому и было сложно эту ошибку найти.
GUILayout.BeginVertical(Styles.nullStyle, GUILayout.ExpandHeight(true));
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
if (Event.current.type == EventType.Repaint)
fullRect = GUILayoutUtility.GetLastRect();
var itemsCount = (int)fullRect.height / height;
var elems = GetVisibleElements(itemsCount);
for (int i = 0; i < elems.Count; i++)
{
GUILayout.BeginArea(new Rect(fullRect.x, fullRect.y + height * i, fullRect.width, height));
DrawElement(elems[i]);
GUILayout.EndArea();
}
Кто не понимает стандартный гуй, поясню что здесь:
- Мы рисуем через GUILayout произвольный прямоугольник, заполняющий максимально возможное пространство (GUILayout тем и отличается от просто класса GUI, что позволяет рисовать абстрактно, а не по точным координатам). На выходе все само растягивается и считается как нужно.
- Далее в fullRect мы записывает последний получившийся прямоугольник (область из первого пункта). Этот метод может срабатывать только когда происходит Repaint.
- По полученному прямоугольнику мы узнаем максимально возможное к отображению количество элементов
- Берем элементы для отображения
- Рисуем каждый элемент в нужной области (BeginArea и EndArea)
Вот примерно такой код и содержит ошибку. Указывает на строчку с BeginArea.
На самом же деле ошибка была в GetLastRect
Чтобы понять ее принцип нужно понимать, что для отрисовки Layout функция OnGUI прогоняется дважды.
На самом же деле ошибка была в GetLastRect
Чтобы понять ее принцип нужно понимать, что для отрисовки Layout функция OnGUI прогоняется дважды.
- Сначала с событием Layout
- Затем с событием Repaint
И конкретно сама ошибка возникает при ресайзе. Что же происходит под капотом?
- На стадии Layout у нас хранились старая область прямоугольника. Так как функция прогонялась в этом режиме, было зафиксировано, что метод нарисует элемент Area некоторое количество раз (например 5).
- На стадии Repaint код прогнался еще раз, в fullRect попали новые координаты и результат в itemsCount получился уже не 5, а допустим 6.
- При прорисовке обнаружается такое недоразумение и это и сообщается в лог в виде ошибки.
Как же избавиться от ошибки? Да очень просто, поставить строчки с расчетом itemsCount перед получением нового прямоугольника:
GUILayout.BeginVertical(Styles.nullStyle, GUILayout.ExpandHeight(true));
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
var itemsCount = (int)fullRect.height / height;
var elems = GetVisibleElements(itemsCount);
if (Event.current.type == EventType.Repaint)
fullRect = GUILayoutUtility.GetLastRect();
for (int i = 0; i < elems.Count; i++)
{
GUILayout.BeginArea(new Rect(fullRect.x, fullRect.y + height * i, fullRect.width, height));
DrawElement(elems[i]);
GUILayout.EndArea();
}
В этом случае сначала посчитается число, а затем примется новый прямоугольник. Не будет разницы между возвратом значения в этих двух прогонах метода - все будет в шоколаде.
Кстати наблюдательный может подумать что после GetLastRect возможно дальше продолжить условие, но это тоже приведет к ошибкам, так как не произойдет нужный для GUILayout-функций просчет координат при событии Layout.
Кстати наблюдательный может подумать что после GetLastRect возможно дальше продолжить условие, но это тоже приведет к ошибкам, так как не произойдет нужный для GUILayout-функций просчет координат при событии Layout.
И что я понял из этого, так это то, что понимать как внутри устроен движок очень даже нужное дело - иначе - ничего серьезного сделать не получится. И ведь это лишь один подводный камень в этом море.
Делаем события во времени для редактора
Стало быть вчера понадобилось мне сделать таймер, так как при прокрутке иерархии нужно было время от времени обновлять содержимое окна.
Именно такой - зажал мышку - обновляет, отжал - перестал.
К несчастью сделать точный способ средствами юнити не представляется возможным. Но доступ к Update все же есть, просто нельзя точно отследить количество прошедшего времени - в документации просто написано, что он вызывается приблизительно 100 раз в секунду.
Именно такой - зажал мышку - обновляет, отжал - перестал.
К несчастью сделать точный способ средствами юнити не представляется возможным. Но доступ к Update все же есть, просто нельзя точно отследить количество прошедшего времени - в документации просто написано, что он вызывается приблизительно 100 раз в секунду.
Ну и собственно код для таймера в редакторе, обновляющего окно каждые полсекунды:
public void StartUpdate()
{
timeDeltaCount = 0;
EditorApplication.update += DoUpdate;
}
public int timeDeltaCount = 0;
public void DoUpdate()
{
timeDeltaCount++;
if (timeDeltaCount == 50)
{
window.Repaint(); //Здесь могут быть любые другие действия
timeDeltaCount = 0;
}
}
public void EndUpdate()
{
EditorApplication.update -= DoUpdate;
}
Вот такие дела.
Табуляция и активные контролы
Поначалу может показаться, что описывание контрола - лишняя и ненужная работа, однако, именно она позволяет создать табуляцию и вообще делать вызовы для конкретного элемента.
Есть много рычагов в Unity, для реализации разных фич связаных с контролами, но я пожалуй расскажу самую основу.
Вот минимальный код для того, чтобы описать нажимаемый контрол, аля кнопка:
Есть много рычагов в Unity, для реализации разных фич связаных с контролами, но я пожалуй расскажу самую основу.
Вот минимальный код для того, чтобы описать нажимаемый контрол, аля кнопка:
var id = GUIUtility.GetControlID("MyControl".GetHashCode(), FocusType.Native, rect); //создаем id для "MyControl"
var eventType = Event.current.GetTypeForControl(id); //достаем его событие
if (eventType == EventType.MouseDown) //Если нажата кнопка мыши
{
if (rect.Contains(Event.current.mousePosition) //и мы внутри контрола
{
GUIUtility.hotControl = id; //Сделать контрол активным
Event.current.Use(); //И пометить событие как использованное
}
}
if (eventType == EventType.MouseUp) //Если отжата кнопка мыши
{
if (GUIUtility.hotControl == id) //И выбран текущий контрол
{
GUIUtility.hotControl = 0; //Очистить активный контрол
Event.current.Use(); //И пометить событие как использованное
}
}
Раздельный if умышленен, так как обычно туда нужно еще что-то дописывать.
Собственно этот простой код решит все проблемы, связанные с тем, от чьего лица мы отпускаем и нажимаем кнопки и совершаем какие-либо действия.
Собственно этот простой код решит все проблемы, связанные с тем, от чьего лица мы отпускаем и нажимаем кнопки и совершаем какие-либо действия.
Всем крепким ребятам, дочитавшим сей текст до конца, и хотя бы попытавшимся понять принципы работы ГУИ в редакторе, о которых я говорил здесь, - большое спасибо за прочтение.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Комментарии пока отсутcтвуют.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.