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

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

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

    Для минусующих: альтернатив этому решению не существует, если нужно узнать на каком сейчас уровне находится окно, то это единственный способ
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 24
    • 0
      Поздравляю, вы только что повесили себе ружье на стену.
      • 0
        Unity позволяет писать на JS, C# и Boo на выбор и прочитав эту статью хотелось бы задать вопрос — существует ли существенная разница в плане возможностей на чем писать? Может ли оказаться что есть вещи какие в Unity можно сделать только на одном из этих языков (к примеру C#) и в принципе невозможно сделать на другом (JS к примеру)? Или любую проблему можно решить на любом из языков, просто немного по разному? Ведь API Unity как я понимаю для всех языков одинаков (в плане возможностей)?
        • 0
          К сожалению я не знаю всех возможностей JavaScript, т.к. ничего серьезного на нем не писал.
          • 0
            На JS можно задать значение для координат трансформа (transform.position.x ,y, z) напрямую (transform.position.x = 50.0f), в C# же придется задавать через Vector3.

            Это из известной мне разницы :)
            • 0
              Ну это уже особенности синтаксиса и типов переменных, мне интереснее функциональные ограничения между языками.
              • 0
                Функциональных ограничений? Не знаю насчет JS/Boo, но C# позволяет «использовать себя полностью», выходя за рамки встроенных возможностей Unity3D.
            • +1
              Лучше использовать C#. JavaScript тут — это JScript от Microsoft, урезанный и стремный.
              • 0
                Для начала ссылка на официальную вики об особенностях местного JS.
                Из того с чем я сталкивался:
                — геттеры и сеттеры в UnityScript (будем его так называть) есть, но их использование иногда не удобно;
                — невозможно передавать в UnityScript объекты в параметрах (в отличии от классического JS).
              • 0
                Если это синглтон, то почему бы вам не сохранить куда-нибудь при старте ссылку на инстанс? Тогда и не нужно будет каждый раз рефлекшн дергать.

                Еще вариант… отслеживать интеракции с окнами и хранить стэк окон.

                П.С.: Ей Богу! Зря вы с рефлекшном завязываетесь! Лучше зайдите с другого бока к проблеме — наверняка есть решение много безопаснее.
                • 0
                  почему бы вам не сохранить куда-нибудь при старте ссылку на инстанс? Тогда и не нужно будет каждый раз рефлекшн дергать.

                  Я об этом написал в примечании, сама функция это пример.

                  Еще вариант… отслеживать интеракции с окнами и хранить стэк окон.

                  Это решение не подходит, ввиду того, что мы не знаем на каком слое окно после вызова. К тому же отследить все варианты изменения глубины окна не представляется возможным.

                  Лучше зайдите с другого бока к проблеме — наверняка есть решение много безопаснее.

                  Нету. Либо гугль, форум юнити, и их документация о нем не знают.
                  Это решение не показало уменьшения производительности или стабильности клиента.
                  • +1
                    Небезопасность тут потоньше. Реализация может измениться и код вдруг начнет падать или работать не так.
                    • 0
                      Юнити не обновляется сама, так что с этим проблем не будет. В любом случае альтернатив этому решению нет.
                      • 0
                        Тоже хотел написать об этом… но вспомнил, что при билде либы рантайма встраиваются в дистриб приложения.
                      • 0
                        > Это решение не показало уменьшения производительности или стабильности клиента.
                        До поры… до времени… Так всегда вначале кажется, что производительность не аффектается. А потом постепенно такие же костыли появляются в других местах либо на этот кастыль наложится другой, который сам по себе ничего не рушит. А вместе полная задница.

                        П.С.: еще вариант — докинг без перекрытия. Уж выстроить то окна рядом друг с другом можно?
                        См. тайловые менеджеры окон
                        • 0
                          Да, запретить перекрывать окна можно. Но для нашего интерфейса это не подходящий вариант.
                    • 0
                      А сделать классы обертки для окон с уникальным ID нельзя?
                      • 0
                        все равно мы не будем знать на каком слое оно сейчас находится
                        • 0
                          А если контролировать это изначально? То есть сделать свой z-buffer для них, свою переключалку по клику курсора?
                          • 0
                            Дело в том что после вывода окна на экран неизвестно на каком оно слое, еще до действий пользователя.
                            • 0
                              Понял :/ Вечно с этим встроенным ГУИ траблы. С 4ой версией надеюсь будет получше
                              • +1
                                Да, текущий Unity GUI доставил нам немало неприятностей.
                      • +1
                        Есть еще одна проблема — рефлекшн выпилен в мобильных платформах и, потенциально, в вебплеере (надо проверять). Т.е. получается непереносимость проекта, что ломает одну из основных «фич» юнити — сборка одного проекта под несколько платформ.
                        • 0
                          Ну этот метод точно работает для Standalone, а для остальных случаев приходится менять концепцию интерфейса.
                        • 0
                          Не придираюсь к конкретному проекту, просто хотел бы обсудить конепцию данного интерфейса.
                          Придерживаюсь мнения, что перетаскиваемые окна не так удобны, как может показаться. Из примеров игр, в которых были реализованы «плавучий» оконный интерфейс вспоминаются Морровинд, ЕВЕ онлайн, Рагнарок онлайн. Из моих наблюдений во всех случаях игрок приходил к тому, что однажды распологал окна, а потом не менял эту схему на протяжении всей игровой «карьеры». Схема обычно придераживалась некоторых правил: окна не наслаивались друг на друга; схожие по функциональности окна (например, сундук и рюкзак) ставились рядом и подгонялись под одну высоту или ширину; чем больше информации в окне, тем больше окно. Словом, я считаю, что если бы за игрока все это сделал (конечно, с крайней вдумчивостью и отсветсвенностью) разработчик или дизайнер интерфейсов, то и игрок бы оказался в плюсе, и от многой лишней функциональности (перетаскивание окон) можно было бы отказаться.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.