This page has been translated automatically.
Видеоуроки
Интерфейс
Основы
Продвинутый уровень
Подсказки и советы
Основы
Программирование на C#
Рендеринг
Профессиональный уровень (SIM)
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Физика
Редактор UnigineEditor
Обзор интерфейса
Работа с ассетами
Контроль версий
Настройки и предпочтения
Работа с проектами
Настройка параметров ноды
Setting Up Materials
Настройка свойств
Освещение
Sandworm
Использование инструментов редактора для конкретных задач
Расширение функционала редактора
Встроенные объекты
Ноды (Nodes)
Объекты (Objects)
Эффекты
Декали
Источники света
Geodetics
World-ноды
Звуковые объекты
Объекты поиска пути
Player-ноды
Программирование
Основы
Настройка среды разработки
Примеры использования
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Плагины
Форматы файлов
Материалы и шейдеры
Rebuilding the Engine Tools
Интерфейс пользователя (GUI)
VR Development
Двойная точность координат
API
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
Работа с контентом
Оптимизация контента
Материалы
Визуальный редактор материалов
Сэмплы материалов
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Учебные материалы

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

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

Шаг 1. Создание контроллера глобального виджета#

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

  1. Создайте новый компонент C++ и назовите его UiElement. Этот компонент обрабатывает вызовы функций выбора и кнопок. Напишите следующий код и сохраните его в своей среде разработки.

    UiElement.h (C++)
    #pragma once
    #include <UnigineComponentSystem.h>
    
    #include <UnigineGame.h>
    
    class UiElement : public Unigine::ComponentBase
    {
    public:
    	// declare constructor and destructor for our class and define a property name. 
    	COMPONENT_DEFINE(UiElement, ComponentBase)
    	// declare methods to be called at the corresponding stages of the execution sequence
    	COMPONENT_INIT(init);
    	COMPONENT_UPDATE(update);
    
    	enum Element { Restart, Exit, None };
    
    	// default type of a UI element
    	Element uiType = Element::None;
    
    	// scale on cursor hover
    	float selectScale = 1;
    
    	// emission color on cursor hover
    	Unigine::Math::vec4 selectEmission = Unigine::Math::vec4_black;
    
    	// default mask for the mouse intersection detection
    	int uiMask = 1;
    
    	static void setOnClickCallback(Unigine::CallbackBase *callback) { onClickCallback = callback; }
    
    protected:
    	void init();
    	void update();
    
    	void onWindowResize();
    	void onDisable();
    	void onLeave();
    	void onEnter();
    
    private:
    	static Unigine::CallbackBase *onClickCallback;
    
    	// ID of the UI element
    	int Id; 
    
    	// ID counter for initialization  
    	static int idCount;
    
    	// counter of selected objects
    	int selectedObjects = 0;
    
    	Unigine::WorldIntersectionPtr intersection;
    
    	bool isSelect = false;
    
    	Unigine::ObjectPtr uiObject = nullptr;
    
    	Unigine::Math::vec3 sourceScale = Unigine::Math::vec3_one;
    	Unigine::Math::vec4 sourceEmission = Unigine::Math::vec4_one;
    };
    UiElement.cpp (C++)
    #include "UiElement.h"
    
    using namespace Unigine;
    using namespace Math;
    
    REGISTER_COMPONENT(UiElement);
    
    CallbackBase *UiElement::onClickCallback = nullptr;
    
    int UiElement::idCount = 0;
    
    void UiElement::init()
    {
    	// set ID
    	Id = idCount;
    	++idCount;
    
    	selectedObjects = 0;
    
    	// get the UI element
    	uiObject = checked_ptr_cast<Object>(node);
    
    	// remember the source scale and emission color
    	sourceScale = node->getScale();
    	if (uiObject)
    		sourceEmission = uiObject->getMaterialParameterFloat4("emission_color", 0);
    
    	intersection = WorldIntersection::create();
    }
    
    void UiElement::update()
    {
    	// taking into account main window position to obtain correct direction
    	Math::ivec2 mouse = Input::getMousePosition();
    
    	// get points for intersection
    	vec3 dir = Game::getPlayer()->getDirectionFromMainWindow(mouse.x, mouse.y);
    	Vec3 p0 = Game::getPlayer()->getWorldPosition();
    	Vec3 p1 = p0 + Vec3(dir * 25.0f);
    
    	// find the intersection
    	ObjectPtr obj = World::getIntersection(p1, p0, uiMask, intersection);
    	if (obj)
    	{
    		// try to get the UI element component and select/deselect it
    		UiElement *uiElement = ComponentSystem::get()->getComponent<UiElement>(obj);
    		if (uiElement && uiElement->Id == Id)
    		{
    			if (!isSelect)
    			{
    				UiElement::onEnter();
    				isSelect = true;
    				++selectedObjects;
    			}
    		}
    		else if (isSelect)
    		{
    			UiElement::onLeave();
    			isSelect = false;
    			--selectedObjects;
    		}
    	}
    	else
    	{
    		if (isSelect)
    		{
    			UiElement::onLeave();
    			isSelect = false;
    			--selectedObjects;
    		}
    	}
    
    	// run the mouse click callback
    	if (isSelect && Input::isMouseButtonDown(Input::MOUSE_BUTTON::MOUSE_BUTTON_LEFT) && onClickCallback)
    		onClickCallback->run(uiType);
    }
    
    void UiElement::onDisable()
    {
    	// deselect an object
    	if (isSelect)
    	{
    		--selectedObjects;
    		if (selectedObjects < 0)
    			selectedObjects = 0;
    
    		isSelect = false;
    		UiElement::onLeave();
    	}
    }
    
    void UiElement::onEnter()
    {
    	// set the visual effect on selection
    	node->setScale(sourceScale * selectScale);
    	uiObject->setMaterialParameterFloat4("emission_color", selectEmission, 0);
    }
    
    void UiElement::onLeave()
    {
    	// remove the visual effect when the UI element is not selected anymore
    	node->setScale(sourceScale);
    	uiObject->setMaterialParameterFloat4("emission_color", sourceEmission, 0);
    }
  2. Создайте новый компонент C++ и назовите его EndWidget. Скопируйте приведенный ниже код и вставьте его в соответствующие файлы в вашем проекте и сохраните их в вашей IDE.

    EndWidget.h (C++)
    #pragma once
    #include <UnigineComponentSystem.h>
    
    #include "LevelManager.h"
    #include "UiElement.h"
    #include <UnigineConsole.h>
    
    class EndWidget : public Unigine::ComponentBase
    {
    public:
    	// declare constructor and destructor for our class and define a property name. 
    	COMPONENT_DEFINE(EndWidget, ComponentBase)
    	// declare methods to be called at the corresponding stages of the execution sequence
    	COMPONENT_INIT(init);
    	
    	// object with the end game message
    	PROP_PARAM(Node, endGameWidget);
    
    protected:
    	void init();
    
    	void endGameEventHandler();
    	void onClickHandler(UiElement::Element uiType);
    
    private:
    	LevelManager *levelManager;
    };
    EndWidget.cpp (C++)
    #include "EndWidget.h"
    
    using namespace Unigine;
    
    REGISTER_COMPONENT(EndWidget);
    
    void EndWidget::init()
    {
    	levelManager = ComponentSystem::get()->getComponent<LevelManager>(World::getNodeByName("level_manager"));
    
    	if (levelManager)
    		levelManager->setEndGameCallback(MakeCallback(this, &EndWidget::endGameEventHandler));
    
    	// set the mouse click handler for UI elements (Restart/Exit)
    	UiElement::setOnClickCallback(MakeCallback(this, &EndWidget::onClickHandler));
    
    	if (endGameWidget)
    	{
    		// hide the end UI
    		endGameWidget->setEnabled(false);
    	}
    }
    
    void EndWidget::endGameEventHandler()
    {
    	// set gui and input
    	Input::setMouseHandle(Input::MOUSE_HANDLE::MOUSE_HANDLE_USER);
    
    	// show the end UI
    	endGameWidget->setEnabled(true);
    }
    
    void EndWidget::onClickHandler(UiElement::Element uiType)
    {
    	// restart the level by reloading the world
    	if (uiType == UiElement::Element::Restart)
    	{
    		Unigine::Console::run("world_reload");
    	}
    
    	// exit the game
    	if (uiType == UiElement::Element::Exit)
    		Engine::get()->quit();
    }
  3. Создайте и запустите решение в вашей среде разработки, чтобы восстановить свойства.
  4. Переключитесь на UnigineEditor и создайте новую Dummy Node. Назовите ее "widgets_controller".
  5. Присвойте свойство EndWidget ноде widgets_controller.

Шаг 2. Настройка глобального виджета#

Финальный виджет предоставляет функциональность кнопок и дополнительную информацию о текущем исходе игры. Чтобы правильно отобразить финальный виджет, расположите его перед камерой.

  1. Перетащите ноду programming_quick_start\ui\ui_plane.node из Asset Browser в мир.
  2. Привяжите ее к камере PlayerDummy. Установите положение ноды ui_plane на следующие значения.

  3. Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
  4. Далее мы добавляем в класс LevelManager проверку условия победы, текст финального виджета и запуск обратного вызова. Давайте также назначим компонент UiElement кнопкам финального виджета прямо здесь, в классе LevelManager.

    Установите значение Ui Type на Restart, включите 6-й бит для параметра Ui Mask (соответствующий маске Intersection кнопки), чтобы правильно обрабатывать пересечения выделения, и установите для параметра Select Scale значение 1.05, чтобы кнопки увеличивались при наведении курсора. Кроме того, установите для параметра Select значение #505050, чтобы кнопка изменила свой цвет.

    Для этого откройте компонент LevelManager в вашей IDE и замените его следующим кодом. Не забудьте сохранить свой код.

    LevelManager.h (C++)
    #pragma once
    #include <UnigineComponentSystem.h>
    
    #include <UnigineWidgets.h>
    #include <UnigineGame.h>
    #include <UnigineString.h>
    //========================== NEW - BEGIN ===============================
    #include "UiElement.h"
    //=========================== NEW - END ================================
    
    class LevelManager : public Unigine::ComponentBase
    {
    public:
    	// declare constructor and destructor for our class and define a property name. 
    	COMPONENT_DEFINE(LevelManager, ComponentBase)
    	// declare methods to be called at the corresponding stages of the execution sequence
    	COMPONENT_INIT(init, 2); // 2nd initialization order
    	COMPONENT_UPDATE(update);
    	COMPONENT_SHUTDOWN(shutdown);
    	// level timer
    	PROP_PARAM(Float, timer, 100.0f);
    //========================== NEW - BEGIN ===============================
    	PROP_PARAM(Node, restartButton);
    	PROP_PARAM(Node, exitButton);
    //=========================== NEW - END ================================
    	void decPhysicalObjectsNum();
    	void setEndGameCallback(Unigine::CallbackBase *callback) { endGameEvent = callback; }
    
    protected:
    	void init();
    	void update();
    	void shutdown();
    
    	void initGUI();
    
    private:
    	Unigine::CallbackBase *endGameEvent = nullptr;
    
    	bool isCounting = true;
    
    	int physicalObjectsNum;
    
    	Unigine::WidgetLabelPtr widget_timer, widget_goal;
    
    	Unigine::ObjectTextPtr endText;
    };
    LevelManager.cpp (C++)
    #include "LevelManager.h"
    
    using namespace Unigine;
    using namespace Math;
    
    REGISTER_COMPONENT(LevelManager);
    
    void LevelManager::init()
    {
    	LevelManager::initGUI();
    
    	// find the object text node of the widget
    	endText = checked_ptr_cast<ObjectText>(Game::getPlayer()->findNode("header_text", 1));
    
    	// count dynamic objects in the level
    	physicalObjectsNum = node->getNumChildren();
    //========================== NEW - BEGIN ===============================
    	// set up the restart button as a UI element
    	if (restartButton)
    	{
    		UiElement* restart_component = ComponentSystem::get()->addComponent<UiElement>(restartButton);
    		restart_component->uiType = UiElement::Element::Restart;
    		restart_component->uiMask = 0x00000040; // 6th bit is set
    		restart_component->selectScale = 1.05f;
    	}
    
    	// set up the exit button as a UI element
    	if (exitButton)
    	{
    		UiElement* exit_component = ComponentSystem::get()->addComponent<UiElement>(exitButton);
    		exit_component->uiType = UiElement::Element::Exit;
    		exit_component->uiMask = 0x00000040; // 6th bit is set
    	}
    //=========================== NEW - END ================================
    }
    
    void LevelManager::initGUI()
    {
    	// get a GUI pointer
    	GuiPtr gui = Gui::getCurrent();
    
    	// create a label widget and set up its parameters
    	widget_timer = WidgetLabel::create(gui, "Time Left:");
    	widget_timer->setPosition(10, 10);
    	widget_timer->setFontColor(vec4_red);
    
    	widget_goal = WidgetLabel::create(gui, "Objects Left: ");
    	widget_goal->setPosition(10, 30);
    	widget_goal->setFontColor(vec4_blue);
    
    	// add widgets to the GUI
    	gui->addChild(widget_timer, Gui::ALIGN_OVERLAP);
    	gui->addChild(widget_goal, Gui::ALIGN_OVERLAP);
    }
    
    void LevelManager::update()
    {
    	// decrease the timer
    	if (isCounting)
    	{
    		timer = timer - Game::getIFps();
    		if (timer <= 0)
    		{
    			//set end game text
    			if (endText) endText->setText("Game Over");
    			if (endGameEvent) endGameEvent->run();
    			isCounting = false;
    		}
    	}
    
    	// show the current time and objects left to clear
    	if (isCounting)
    	{
    		widget_timer->setText(String::format("Time Left: %.2f s", timer.get()));
    		widget_goal->setText(String::format("Objects Left: %d", physicalObjectsNum));
    	}
    	//hide the widgets on endgame
    	else
    	{
    		widget_timer->setEnabled(false);
    		widget_goal->setEnabled(false);
    	}
    	
    	//win
    	if (physicalObjectsNum <= 0)
    	{
    		if (endText) endText->setText("Success!");
    		if (endGameEvent) endGameEvent->run();
    		isCounting = false;
    	}
    }
    
    void LevelManager::shutdown()
    {
    	widget_timer.deleteLater();
    	widget_goal.deleteLater();
    }
    
    void LevelManager::decPhysicalObjectsNum()
    {
    	physicalObjectsNum--;
    }
  5. Создайте и запустите решение для восстановления файла свойств для компонента LevelManager.
  6. Переключитесь на UnigineEditor, выберите ноду ui_plane в окне World Nodes и щелкните правой кнопкой мыши и выберите Unpack To Node Content, чтобы связать ноды кнопок с компонентом LevelManager.
  7. Затем перетащите кнопки (restart_button и exit_button) в соответствующие поля свойства LevelManager.

  8. Перетащите ноду ui_plane в поле End Game Widget под компонентом EndWidget ноды widgets_controller.

  9. Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
  10. Переключитесь на свою IDE, затем создайте и запустите игру в своей IDE, чтобы увидеть пользовательский интерфейс в действии.
Последнее обновление: 20.12.2023
Build: ()