Как стать автором
Обновить

Unity3D 3.x Получение текущего активного окна

Время на прочтение3 мин
Количество просмотров8.2K
Недавно перед нашей командой встала довольно простая задача. Нам нужно было сделать перетаскивание вещи из инвентаря в другие окна (эквип, сундук). Если два окна находятся друг над другом, то вещь должна упасть в то окно, которое выше.

Бегло осмотрев список свойств в классе 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 в переменной класса, чтобы каждый раз не терять время на выяснении кучи типов и полей.

Для минусующих: альтернатив этому решению не существует, если нужно узнать на каком сейчас уровне находится окно, то это единственный способ
Теги:
Хабы:
Всего голосов 24: ↑19 и ↓5+14
Комментарии24

Публикации

Истории

Работа

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург