Scene View в Unity3D является одним из самых необходимых элементов интерфейса. Каждый, кто хоть раз запускал Unity3D пользовался Scene View для визуальной расстановки объектов на сцене, а также для их настройки. Расширение функционала Scene View может понадобиться для создания собственного редактора уровней, редактирования mesh’а, создания собственных gizmos и много другого. Стоит заметить, что при использовании Terrain в вашем проекте, его редактирование (рисование текстур, изменение высот, а также посадка деревьев и растительности) осуществляется при помощи Scene View.
Для того, чтобы иметь возможность писать скрипты работающие в Scene view в первую очередь класс с которым вы работаете должен быть унаследован от Editor или EditorWindow, что подразумевает подключение namespace UnityEditor. Это дает доступ к нескольким «магическим» методам Unity3D, таким как OnGUI() и OnSceneGUI(). Метод OnSceneGUI дает возможность Editor'у управлять событиями Scene View.
Небольшое лирическое отступление. Среди разработчиков бытует мнение, что «магические» методы (Update, Start, OnSceneGUI и другие) реализованы по средствам System.Reflection в C#, однако есть информация, что это не так, и за их работу стоит благодарить C++ ядро Unity3D. В целом статья будет полезна всем, кто грешит множественными Update() в своем коде.
Вернемся к перехвату событий. Идея заключается в том, чтобы обозначить объект, для которого мы разрабатываем функционал, как элемент интерфейса Unity, на равне с Label или Button из IMGUI.
В api существует класс Event, который используется для различных GUI событий, например нажатия клавиш, кнопок мыши, событий рендеринга и layout’а Unity.
Помимо Event для перехвата кликов понадобится такая вещь как control. В качестве control в Unity может выступать любой элемент IMGUI. К примеру Button или Label. Для того, чтобы ваш объект перехватывал клик, необходимо сказать Unity, что он тоже является control и собирается участвовать в обработке событий. Для этого воспользуемся классом GUIUtility.
Каждый control должен обладать собственным уникальным id, с помощью которого Unity получит всю необходимую информацию о нем. id представляет собой int, однако стоит заметить, что он должен быть уникальным. Вы можете сгенерировать случайное число (отличное от 0) и считать его id вашего control, но при этом нет гарантии, что в системе уже нет control с таким же id, и в таком случае клик уйдет не к тому элементу, на который вы рассчитывали. Для создания уникального id используем метод из класса GUIUtility
public static int GUIUtility.GetControlID(FocusType focus)
Параметр, принимаемый на вход этим методом отвечает за возможность control получать какой-либо ввод с клавиатуры. Информацию о нем, можно получить в документации. Так как ввод с клавиатуры в случае использовании Scene View не нужен, подойдет значение FocusType.Passive.
int controlId = GUIUtility.GetControlID(FocusType.Passive); //получение уникального id для нашего элемента интерфейса.
Теперь, когда мы разобрались с получением id, настало время разобраться с перехватом событий. Мы можем получить информацию о текущем событии с помощью свойства Event.current. Чтобы получить тип события, которое произошло в Unity нужно сделать следующее
int controlId = GUIUtility.GetControlID(FocusType.Passive);
Event.current.GetTypeForControl(controlId)
Данный вызов вернет значение типа EventType для нашего control. Далее нужно только определиться какие именно события вам нужны и перехватить их. Непосредственно перехват события осуществляется следующим образом
GUIUtility.hotControl = controlId;
В случае с кликом мыши, этим вы говорите что ваш control является «горячим», то есть он в первую очредь будет реагировать на события мыши, и они не пойдут дальше к другим conltrol.
Данный аспект подробно описан в документации. Чтобы вернуть доступ к событиям мыши к другим control необходимо сделать
GUIUtility.hotControl = 0;
Этим вы скажете, что следующие события мыши может перехватить любой другой control. Также события будут доступны для других control, если вы их не используете. То есть не пометите как Used
Event.current.Use();
Приведу полный пример скрипта, обрабатывающего клики по Scene View
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(MyCustomComponent))]
public class MyCustomEditor : Editor
{
void OnSceneGUI()
{
int controlId = GUIUtility.GetControlID(FocusType.Passive);
switch (Event.current.GetTypeForControl(controlId))
{
case EventType.MouseDown:
GUIUtility.hotControl = controlId;
//Ваша логика использования события MouseDown
//Левая кнопка мыши
if(Event.current.button == 0)
{
// . . .
}
//Правая кнопка мыши
if(Event.current.button == 1)
{
// . . .
}
//Используем событие
Event.current.Use();
break;
case EventType.MouseUp:
//Возвращаем другим control доступ к событиям мыши
GUIUtility.hotControl = 0;
Event.current.Use();
break;
}
}
У кого-то может возникнуть вопрос о надобности описанного выше материала. В качестве примера можно привести создание редактора 2d тайловых уровней. Можно написать подобный кастомный редактор для класса карты, а при перехвате событий получать координаты мыши для определения конкретного дочернего элемента, по которому был произведен клик. Определить конкретный элемент можно сделав Raycast, который в Scene View отработает абсолютно также, как и в ходе работы вашей игры.
На этом разрешите откланяться, надеюсь, что данная статья окажется полезной для кого-нибудь.
Автор: vmchar