Работа с UI в Unigine. Версия 2.13


photo

Recommended Posts

Искренне надеюсь что этот туториал в скором времени устареет и у нас будет толковая система UI с которой просто работать, но при этом будет обладать всей мощностью графической части движка.


Для версии 2.13 есть две системы UI. Одна основана на XML, вторая на основе верстке из кода. На момент написания поста, все что есть об UI это (обратите внимание что некоторые ссылки на версию 2.12. Об этом вам еще напишет окошко документации, что просто прекрасно):


Создание пользовательского интерфейса

графический интерфейс пользователя

Файлы пользовательского интерфейса

Контейнеры

Виджеты


Значит исходя из документации я понял следующее:
1) Все это хозяйство работает на UnigineScript.
2) Для XML надо использовать наследование от SystemLogic/WorldLogic (для С#).
3) Файлы SystemLogic/WorldLogic должны находится в неведомом мне месте и выполняться неведомым мне способом.

 

Верстка интерфейса из кода.
Тут из скрипта пишем обычный интерфейс, манипулируя классами вот от сюда https://developer.unigine.com/en/docs/2.13/api/library/gui/?rlang=cs

Все очень просто, собственно алгоритм.
1) Пишем например вот этот скрипт из спойлера

Spoiler





using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "ваш ID скрипта")]
public class UI : Component
{
	private UserInterface ui;

	private void Init()
	{
		//пример с просто кнопкой
		// getting a pointer to the system GUI
		Gui gui = Gui.Get();

		// creating a button widget and setting its caption
		WidgetButton widget_button = new WidgetButton(gui, "Delete");

		// setting a tooltip
		widget_button.SetToolTip("Delete object(s)");

		// rearranging button size
		widget_button.Arrange();

		// setting button position
		widget_button.SetPosition(10, 10);

		// setting onButtonClicked function to handle CLICKED event
		widget_button.AddCallback(Gui.CALLBACK_INDEX.CLICKED, OnButtonClicked);

		// adding created button widget to the system GUI
		gui.AddChild(widget_button, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);
	}

	void OnButtonClicked()
	{
		Log.Message(this + ". Жмяк");
		
		return;
	}
}

 


2) Вешаем его куда нибудь
3) Запускаем проект
4) Любуемся кнопочкой в левом верхнем углу экрана. Иногда ее жмякаем.

В общем то ничего не обычного, к примеру в Unity это существовало давным давно и мне даже нравилось с этим работать, поскольку все контролируешь из кода, так что все работает именно так как тебе надо, при этом по факту пользуешься значениями как обычными переменными. Минус в том, что выглядит все это по современным меркам, убого. И с этим ничего не поделать.

Все это так же можно отрисовывать на ObjectGui, (подсмотрено вот от сюда Per world GUI scripts? ). 
1) Создаем на сцене (world) ObjectGui. Это то место где будет отрисовываться наш интерфейс.
2) Пишем например вот этот скрипт

Spoiler





using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "8386be9c3774c94b3de40e2ea127ffb554060d90")]
public class UI : Component
{
  	// This is where you'll drag the ObjectGUI to in the editor.
	[ShowInEditor]
	public ObjectGui oui;

	private void Init()
	{
		//пример с GetGui 
		Gui gui = oui.GetGui();

		// creating a button widget and setting its caption
		WidgetButton widget_button = new WidgetButton(gui, "Delete");

		// setting a tooltip
		widget_button.SetToolTip("Delete object(s)");

		// rearranging button size
		widget_button.Arrange();

		// setting button position
		widget_button.SetPosition(10, 10);

		// setting onButtonClicked function to handle CLICKED event
		widget_button.AddCallback(Gui.CALLBACK_INDEX.CLICKED, OnButtonClicked);

		// adding created button widget to the system GUI
		gui.AddChild(widget_button, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);
	}

	void OnButtonClicked()
	{
		Log.Message(this + ". Жмяк");
		
		return;
	}
}

 


3) Вешаем его куда нибудь, кидаем в него ObjectGui
4) Запускаем проект
5) Любуемся кнопочкой в левом верхнем углу ObjectGui. Можно пожмякать на нее.

 

Есть некоторая сложность в выборе виджетов. Пока я не выучил что и как выглядит, для себя я выбрал следующий алгоритм.

1) Как все выглядит в общем, можно посмотреть в SDK > Samples > API > Widgets > UserInterface. Там же если нажать Open folder можно найти UserInterface.cs, в корневом каталоге и user_interface.ui, в папке data. Обычно верста происходит так - сначала задается общее окно, затем ее разметка, затем прописываются элементы окна (кнопки, текст и т.п.). В коде все это пишется сверху вниз. То что слева прописывается до элементов справа. Т.е. примерно все выглядит так - Окно > Заголовок (может быть в отдельном окне) > Группа окон > Окно 1 > Текст который будет в окне слева > Картинка которая будет в окне справа > Кнопка которая будет снизу под всеми окнами (может быть в отдельном окне). 

Соответственно в user_interface.ui схожая структура так что зная ее найти по примерному названию (например <button name="button_OK" align="bottom"> показывает что надо искать по ключевому слову "button") элементы из примера UserInterface довольно просто.

2) Как только нашли название, идем вот сюда GUI-Related Classes, и через ctrl+F ищем наш виджет button (WidgetButton Class).

3) Если лень открывать UserInterface, потом user_interface.ui, а потом искать в GUI-Related Classes, то можно пойти в UI Files, в дереве слева там будет еще три вкладки, нам нужны Containers и Widgets, через поиск найти интересующий вас элемент с картинкой. Ну не знаю, допустим это будет vscroll. Там же есть ссылка на интересующий нас элемент в GUI-Related Classes - Unigine.WidgetScroll Class.

 

Когда вы нашли все что вам нужно и теперь надо задать расположение, но вы по какой-то причине не хотите привязывать все это к координатам, то идем вот сюда Unigine.Gui Class, спускаемся до Variables и ищем к чему надо все это дело привязывать например вот так: 

gui.AddChild(widget_button, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);

Теперь кнопка будет расположена по центру экрана. Если это будет окно:

widget_window.AddChild(widget_button, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);

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

Затем, когда мы кнопке добавляем метод addCallback (который внезапно принадлежит классу не WidgetButton Class, а Widget Class), то можно передавать в вызываемую функцию переменные вот таким образом:

widget_button.AddCallback(Gui.CALLBACK_INDEX.CLICKED, () => OnButtonClicked (something_there));

Так что теперь при событии "Gui.CALLBACK_INDEX.CLICKED" вызовется OnButtonClicked (something_there) со значением something_there. Таким образом вы можете создать с десяток кнопок, в котором значение something_there будет уникальным благодаря чему OnButtonClicked будет выполнять именно то что вам нужно от кнопки.

Любопытно что если вы засунете Log.Message в OnButtonClicked, ну например вот так:

Log.Message(this + ". something_there = {0} \n", something_there);

то сообщение появится, но значение у something_there не будет. Однако оно там есть (еще никогда раньше вы не были так близки к суслику).

 

О позиционировании.

Хех, совсем забыл что при такой системе надо прописывать ручками позиционирование элементов UI (вот тут сейчас пользователи Unity буквально прочувствуют насколько их система uGUI ушла далеко вперед). В общем все тоже довольно просто - 

Для того чтобы задать точное местоположение вашему UI элементу на экране, есть три способа:

  1. Задать строгие координаты 

  2. Вычесть строгие координаты от размера экрана/окна

  3. Использовать соотношения от экрана/окна

Взять размеры экрана можно вот отсюда

Высота - https://developer.unigine.com/en/docs/2.13/api/library/gui/class.gui?rlang=cs#Height

Ширина - https://developer.unigine.com/en/docs/2.13/api/library/gui/class.gui?rlang=cs#Width

Важно! Координаты экрана всегда начинаются от левого верхнего угла. Ось X - влево, ось Y - вниз.

Внимание! Со строгой привязкой координат окна и содержимого окна, вы не застрахованы от того, что они в какой-то момент будут вылезать за пределы экрана/окна. Всегда используйте соотношения настолько, насколько это вообще возможно.

Псевдо код (это такой, работоспособность которого не проверялось):

Spoiler

 



		Gui gui = Gui.Get();
		
		/*
		Важно помнить что координаты экрана ВСЕГДА начинаются с левого верхнего угла - ось X вправо, ось У вниз.
		*/

		//узнаем размеры экрана. Для русскоязычных людей тут заключается опасность - horizontal и Height имеют сходство по буквам,
		//однако обозначают разные оси экрана. Главное не перепутать)
		int horizontal_screen = gui.Width;
		int vertical_screen = gui.Height;

		//И тут считаем какого размера будет окно UI (я привык считать сначала вертикаль, потом уже горизонталь)
		//Окно со строгой привязкой к координатам начиная от левого верхнего угла экрана
		int vertical_window = 200;
		int horizontal_window = 200;

		//Окно со строгой привязкой от левого нижнего угла экрана
		vertical_window = vertical_screen - 200;
		horizontal_window = horizontal_window - 200;

		//Окно со строгой привязкой к соотношению от размера экрана
		//В данном случае окно будет иметь размер в половину размера экрана
		vertical_window = vertical_screen / 2;
		horizontal_window = horizontal_window / 2;

		WidgetLabel widget_label_point = new WidgetLabel(gui,"+");
		WidgetWindow widget_window_bar_player = new WidgetWindow(gui, "окно_здоровья_персонажа", horizontal_window, vertical_window);
		WidgetLabel widget_label_health = new WidgetLabel(gui, "здоровье");

		//Однако не надо забывать о локализации и сразу же встраивать ее в код
		//WidgetWindow widget_window_bar_player = new WidgetWindow(gui, Languages_script.окно_здоровья_персонажа, horizontal_window, vertical_window);
		//WidgetLabel widget_label_health = new WidgetLabel(gui, Languages_script.здоровье);

		//узнаем итоговое разрешение окна
		vertical_window = widget_window_bar_player.Height;
		horizontal_window = widget_window_bar_player.Width;

		Log.Message(this + ". horizontal_window = {0} \n", horizontal);
		Log.Message(this + ". vertical_window = {0} \n", vertical);

		//привязываем в окне widget_window_bar_player, widget_label_health по горизонтали 10 пикселей, по вертикали 100 пикселей от нижнего края окна
		widget_label_health.SetPosition(10, vertical - 100);
		
		//дочерим все это дело
		gui.AddChild(widget_label_point, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);
		widget_window_bar_player.AddChild(widget_label_health, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);
		gui.AddChild(widget_window_bar_player, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);

Верстка интерфейса из XML

Однако, можно верстать интерфейс из XML. Логика разработчиков понятна — язык для верстки и создавался, но вот его визуальное восприятие...

Пока что не понятно как со всем этим XML работать, но по факту это тоже самое что и описанное выше, с той лишь разницей что используется другой язык и вы получаете себе на попу геморой в виде синхронизации всего этого хозяйства со скриптами в движке. На данный момент я не увидел причин почему его надо использовать. Конечно как разберусь добавлю алгоритм.

 

Все это хорошо до тех пор пока вам не придется делать действительно сложные интерфейсы, например инвентаря с динамической генерацией ячеек в виде кнопок. Вот тогда этот на вид относительно простой код разрастется до сотен строк со своими классами и всякими конструкторами разобраться в котором будет довольно тяжко. Люди которые пришли с Unity конечно будут в ужасе. По этому там разработчики пошли другим путем - создание интерфейса из объектов на сцене. Надеюсь здесь тоже будет нечто подобное. В принципе ничего сложного в этом нет и сейчас в Unigine можно создать 3D интерфейс, используя всю мощь графической части движка. Проблема будет одна - масштабирование относительно экрана. Если ее не прописать ручками, он постоянно будет куда то уезжать точно так же как и при использовании ObjectGui, так как "Basically, ObjectGui is a flat display to which a player can come to and click some buttons". Или придется делать интерфейс под одно разрешение экрана, или несколько интерфейсов под разные разрешение (как когда то под Андроид. Темные, не хорошие времена...). 

Edited by nikolay.sykharev
  • Like 4
  • Thanks 2
Link to post

Обновление темы.

Есть некоторая сложность в выборе виджетов. Пока я не выучил что и как выглядит, для себя я выбрал следующий алгоритм.

1) Как все выглядит в общем, можно посмотреть в SDK > Samples > API > Widgets > UserInterface. Там же если нажать Open folder можно найти UserInterface.cs, в корневом каталоге и user_interface.ui, в папке data. Обычно верста происходит так - сначала задается общее окно, затем ее разметка, затем прописываются элементы окна (кнопки, текст и т.п.). В коде все это пишется сверху вниз. То что слева прописывается до элементов справа. Т.е. примерно все выглядит так - Окно > Заголовок (может быть в отдельном окне) > Группа окон > Окно 1 > Текст который будет в окне слева > Картинка которая будет в окне справа > Кнопка которая будет снизу под всеми окнами (может быть в отдельном окне). 

Соответственно в user_interface.ui схожая структура так что зная ее найти по примерному названию (например <button name="button_OK" align="bottom"> показывает что надо искать по ключевому слову "button") элементы из примера UserInterface довольно просто.

2) Как только нашли название, идем вот сюда GUI-Related Classes, и через ctrl+F ищем наш виджет button (WidgetButton Class).

3) Если лень открывать UserInterface, потом user_interface.ui, а потом искать в GUI-Related Classes, то можно пойти в UI Files, в дереве слева там будет еще три вкладки, нам нужны Containers и Widgets, через поиск найти интересующий вас элемент с картинкой. Ну не знаю, допустим это будет vscroll. Там же есть ссылка на интересующий нас элемент в GUI-Related Classes - Unigine.WidgetScroll Class.

 

Когда вы нашли все что вам нужно и теперь надо задать расположение, но вы по какой-то причине не хотите привязывать все это к координатам, то идем вот сюда Unigine.Gui Class, спускаемся до Variables и ищем к чему надо все это дело привязывать например вот так: 

gui.AddChild(widget_button, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);

Теперь кнопка будет расположена по центру экрана. Если это будет окно:

widget_window.AddChild(widget_button, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);

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

Затем, когда мы кнопке добавляем метод addCallback (который внезапно принадлежит классу не WidgetButton Class, а Widget Class), то можно передавать в вызываемую функцию переменные вот таким образом:

widget_button.AddCallback(Gui.CALLBACK_INDEX.CLICKED, () => OnButtonClicked (something_there));

Так что теперь при событии "Gui.CALLBACK_INDEX.CLICKED" вызовется OnButtonClicked (something_there) со значением something_there. Таким образом вы можете создать с десяток кнопок, в котором значение something_there будет уникальным благодаря чему OnButtonClicked будет выполнять именно то что вам нужно от кнопки.

Любопытно что если вы засунете Log.Message в OnButtonClicked, ну например вот так:

Log.Message(this + ". something_there = {0} \n", something_there);

то сообщение появится, но значение у something_there не будет. Однако оно там есть (еще никогда раньше вы не были близки так к суслику).

  • Like 1
Link to post

Обновление темы.

О позиционировании.

Хех, совсем забыл что при такой системе надо прописывать ручками позиционирование элементов UI (вот тут сейчас пользователи Unity буквально прочувствуют насколько их система ушла далеко вперед). В общем все тоже довольно просто - 

Для того чтобы задать точное местоположение вашему UI элементу на экране, есть три способа:

  1. Задать строгие координаты 

  2. Вычесть строгие координаты от размера экрана/окна

  3. Использовать соотношения от экрана/окна

Взять размеры экрана можно вот отсюда

Высота - https://developer.unigine.com/en/docs/2.13/api/library/gui/class.gui?rlang=cs#Height

Ширина - https://developer.unigine.com/en/docs/2.13/api/library/gui/class.gui?rlang=cs#Width

Важно! Координаты экрана всегда начинаются от левого верхнего угла. Ось X - влево, ось Y - вниз.

Внимание! Со строгой привязкой координат окна и содержимого окна, вы не застрахованы от того, что они в какой-то момент будут вылезать за пределы экрана/окна. Всегда используйте соотношения настолько, насколько это вообще возможно.

Псевдо код (это такой, работоспособность которого не проверялось):

Spoiler

 


		Gui gui = Gui.Get();
		
		/*
		Важно помнить что координаты экрана ВСЕГДА начинаются с левого верхнего угла - ось X вправо, ось У вниз.
		*/

		//узнаем размеры экрана. Для русскоязычных людей тут заключается опасность - horizontal и Height имеют сходство по буквам,
		//однако обозначают разные оси экрана. Главное не перепутать)
		int horizontal_screen = gui.Width;
		int vertical_screen = gui.Height;

		//И тут считаем какого размера будет окно UI (я привык считать сначала вертикаль, потом уже горизонталь)
		//Окно со строгой привязкой к координатам начиная от левого верхнего угла экрана
		int vertical_window = 200;
		int horizontal_window = 200;

		//Окно со строгой привязкой от левого нижнего угла экрана
		vertical_window = vertical_screen - 200;
		horizontal_window = horizontal_window - 200;

		//Окно со строгой привязкой к соотношению от размера экрана
		//В данном случае окно будет иметь размер в половину размера экрана
		vertical_window = vertical_screen / 2;
		horizontal_window = horizontal_window / 2;

		WidgetLabel widget_label_point = new WidgetLabel(gui,"+");
		WidgetWindow widget_window_bar_player = new WidgetWindow(gui, "окно_здоровья_персонажа", horizontal_window, vertical_window);
		WidgetLabel widget_label_health = new WidgetLabel(gui, "здоровье");

		//Однако не надо забывать о локализации и сразу же встраивать ее в код
		//WidgetWindow widget_window_bar_player = new WidgetWindow(gui, Languages_script.окно_здоровья_персонажа, horizontal_window, vertical_window);
		//WidgetLabel widget_label_health = new WidgetLabel(gui, Languages_script.здоровье);

		//узнаем итоговое разрешение окна
		vertical_window = widget_window_bar_player.Height;
		horizontal_window = widget_window_bar_player.Width;

		Log.Message(this + ". horizontal_window = {0} \n", horizontal);
		Log.Message(this + ". vertical_window = {0} \n", vertical);

		//привязываем в окне widget_window_bar_player, widget_label_health по горизонтали 10 пикселей, по вертикали 100 пикселей от нижнего края окна
		widget_label_health.SetPosition(10, vertical - 100);
		
		//дочерим все это дело
		gui.AddChild(widget_label_point, Gui.ALIGN_CENTER | Gui.ALIGN_FIXED);
		widget_window_bar_player.AddChild(widget_label_health, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);
		gui.AddChild(widget_window_bar_player, Gui.ALIGN_OVERLAP | Gui.ALIGN_FIXED);

 

  • Like 1
Link to post