Создание пользовательского интерфейса
Финальный виджет должен быть виден, когда игра закончится. Пользователь сможет перезапустить игру или выйти из приложения с помощью соответствующих кнопок.
Шаг 1. Создание контроллера глобального виджета#
Нода контроллера виджета будет содержать обработчики для различных типов элементов пользовательского интерфейса и показывать пользовательский интерфейс в конце игры.
Создайте новый компонент C++ и назовите его UiElement. Этот компонент обрабатывает вызовы функций выбора и кнопок. Напишите следующий код и сохраните его в своей среде разработки.
#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; };
#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); }
Создайте новый компонент C++ и назовите его EndWidget. Скопируйте приведенный ниже код и вставьте его в соответствующие файлы в вашем проекте и сохраните их в вашей IDE.
#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; };
#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(); }
- Создайте и запустите решение в вашей среде разработки, чтобы восстановить свойства.
- Переключитесь на UnigineEditor и создайте новую Dummy Node. Назовите ее "widgets_controller".
- Присвойте свойство EndWidget ноде widgets_controller.
Шаг 2. Настройка глобального виджета#
Финальный виджет предоставляет функциональность кнопок и дополнительную информацию о текущем исходе игры. Чтобы правильно отобразить финальный виджет, расположите его перед камерой.
- Перетащите ноду programming_quick_start\ui\ui_plane.node из Asset Browser в мир.
-
Привяжите ее к камере PlayerDummy. Установите положение ноды ui_plane на следующие значения.
- Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
-
Далее мы добавляем в класс LevelManager проверку условия победы, текст финального виджета и запуск обратного вызова. Давайте также назначим компонент UiElement кнопкам финального виджета прямо здесь, в классе LevelManager.
Установите значение Ui Type на Restart, включите 6-й бит для параметра Ui Mask (соответствующий маске Intersection кнопки), чтобы правильно обрабатывать пересечения выделения, и установите для параметра Select Scale значение 1.05, чтобы кнопки увеличивались при наведении курсора. Кроме того, установите для параметра Select значение #505050, чтобы кнопка изменила свой цвет.
Для этого откройте компонент LevelManager в вашей IDE и замените его следующим кодом. Не забудьте сохранить свой код.
#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; };
#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--; }
- Создайте и запустите решение для восстановления файла свойств для компонента LevelManager.
- Переключитесь на UnigineEditor, выберите ноду ui_plane в окне World Nodes и щелкните правой кнопкой мыши и выберите Unpack To Node Content, чтобы связать ноды кнопок с компонентом LevelManager.
-
Затем перетащите кнопки (restart_button и exit_button) в соответствующие поля свойства LevelManager.
-
Перетащите ноду ui_plane в поле End Game Widget под компонентом EndWidget ноды widgets_controller.
- Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
- Переключитесь на свою IDE, затем создайте и запустите игру в своей IDE, чтобы увидеть пользовательский интерфейс в действии.