Недавно перед нашей командой встала довольно простая задача. Нам нужно было сделать перетаскивание вещи из инвентаря в другие окна (эквип, сундук). Если два окна находятся друг над другом, то вещь должна упасть в то окно, которое выше.
Бегло осмотрев список свойств в классе GUI я не нашел чего-либо подходящего, потом я осмотрел GUIUtility, и даже заглянул в GUILayout. Вообщем такого свойства нигде не было. Гугление по этому запросу выдает несколько вопросов в Q&A и пару скудных постов на офф. форуме которые заканчиваются ответами в стиле «так сделать нельзя, но можно вручную отслеживать по какому окну нажали мышкой и заполнять переменную активного окна самостоятельно».
Нам не подошло ничего из того что там предлагали, но один парень натолкнул меня на интересную мысль. Мы пишем код на C#, а значит можем пользоваться всеми плюсами этого языка, в том числе и С# Reflection
Кишки
Скачав мой любимый Dis#, я сразу полез в код функции GUI.Window
public static Rect Window(int id, Rect clientRect, GUI.WindowFunction func, string text)
{
return GUI.DoWindow(id, clientRect, func, GUIContent.Temp(text), GUI.skin.window, true);
}
internal static Rect DoWindow(int id, Rect clientRect, GUI.WindowFunction func, GUIContent title, GUIStyle style, bool forceRectOnLayout)
{
GUIUtility.CheckOnGUI();
GUI._Window _window = (GUI._Window)GUI._WindowList.instance.windows[id];
if (_window == null)
{
_window = new GUI._Window(id);
GUI._WindowList.instance.windows[id] = _window;
GUI.s_LayersChanged = true;
}
if (!_window.moved)
_window.rect = clientRect;
_window.moved = false;
_window.opacity = 1.0F;
_window.style = style;
_window.title.text = title.text;
_window.title.image = title.image;
_window.title.tooltip = title.tooltip;
_window.func = func;
_window.used = true;
_window.enabled = GUI.enabled;
_window.color = GUI.color;
_window.backgroundColor = GUI.backgroundColor;
_window.matrix = GUI.matrix;
_window.skin = GUI.skin;
_window.contentColor = GUI.contentColor;
_window.forceRect = forceRectOnLayout;
return _window.rect;
}
Ага, значит есть список окон, осталось выяснить в какой последовательности они отрисовываются, для этого заглянем в функцию GUI.BringWindowToFront
public static void BringWindowToFront(int windowID)
{
GUIUtility.CheckOnGUI();
GUI._Window _window1 = GUI._WindowList.instance.Get(windowID);
if (_window1 != null)
{
int i = 0;
foreach (GUI._Window _window2 in GUI._WindowList.instance.windows.Values)
{
if (_window2.depth < i)
i = _window2.depth;
}
_window1.depth = i - 1;
GUI.s_LayersChanged = true;
}
}
Все понятно, в классе GUI есть синглтон класс _WindowList у которого есть список окон. У каждого окна есть Depth. Отрисовка происходит в порядке убывания Depth. Все что осталось узнать это какого типа этот список.
internal sealed class _WindowList
{
internal Hashtable windows;
internal static GUI._WindowList instance;
.......
Вот и узнали :)
Пишем функцию для выковыривания добра
Функция хорошо прокомментирована и надеюсь не нуждается в пояснении.
/// <summary>
/// Функция определяет самое верхнее окно из списка
/// </summary>
/// <returns>
/// ID самого верхнего окна
/// </returns>
/// <param name='id_list'>
/// Список ID окон
/// </param>
int GetTopmostId(List<int> id_list)
{
//Получаем тип GUI
Type guiType = typeof(GUI);
//Получаем тип списка окон
Type windowListType = guiType.Assembly.GetType("UnityEngine.GUI+_WindowList");
//Получаем поле instance списка, в котором хранится его экземпляр (это синглтон)
FieldInfo windowListInstanceField = windowListType.GetField("instance", BindingFlags.NonPublic | BindingFlags.Static);
//Получаем значение поля, теперь нас есть экземпляр списка
object windowListInstance = windowListInstanceField.GetValue(null);
//Получаем поле спика с окнами
FieldInfo windowsField = windowListType.GetField("windows", BindingFlags.NonPublic | BindingFlags.Instance);
//Получаем сам список окон типа Hashtable
Hashtable hashtable = windowsField.GetValue(windowListInstance) as Hashtable;
//Осталось перебрать его и найти верхнее
int min = -1;
int window_id = -1;
foreach(DictionaryEntry entry in hashtable)
{
int key = (int)entry.Key;
if (id_list.Contains(key)) //сравнивать только если окно в нашем списке
{
//получаем значение поля глубина у окна
int depth = (int)entry.Value.GetType().GetField("depth", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(entry.Value);
if (min < 0 || depth < min)
{
min = depth;
window_id = key;
}
}
}
return window_id;
}
Примечание: если вы собираетесь вызывать функцию каждый OnGUI() event, то рекомендую разбить ее на две части, и хранить Hashtable в переменной класса, чтобы каждый раз не терять время на выяснении кучи типов и полей.
Автор: agasper