Создание пользовательского интерфейса
The end widget should be visible when the game is over. The user will be able to restart the game or exit the application via the corresponding buttons.Финальный виджет должен быть виден, когда игра закончится. Пользователь сможет перезапустить игру или выйти из приложения с помощью соответствующих кнопок.
Step 1. Make the World Widget ControllerШаг 1. Создание контроллера глобального виджета#
The widget controller node will specify the handlers for different types of UI elements and show the UI at the end of the game.Нода контроллера виджета будет содержать обработчики для различных типов элементов пользовательского интерфейса и показывать пользовательский интерфейс в конце игры.
Create a new C++ component and call it UiElement. This component handles the selection and button functions calls. Write the following code and save your code in your IDE.Создайте новый компонент C++ и назовите его UiElement. Этот компонент обрабатывает вызовы функций выбора и кнопок. Напишите следующий код и сохраните его в своей среде разработки.
#pragma once #include <UnigineComponentSystem.h> #include <UnigineApp.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() { // get points for intersection vec3 dir = Game::getPlayer()->getDirectionFromScreen(); vec3 p0 = Game::getPlayer()->getWorldPosition(); vec3 p1 = p0 + 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); }
Create a new C++ component and call it EndWidget. Copy the code below and paste it to the corresponding files in your project and save them in your IDE.Создайте новый компонент C++ и назовите его EndWidget. Скопируйте приведенный ниже код и вставьте его в соответствующие файлы в вашем проекте и сохраните их в вашей IDE.
#pragma once #include <UnigineComponentSystem.h> #include "LevelManager.h" #include "UiElement.h" #include <UnigineConsole.h> #include <UnigineApp.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) App::exit(); }
- Build and run the solution in your IDE to regenerate the properties.Создайте и запустите решение в вашей среде разработки, чтобы восстановить свойства.
- Switch to the UnigineEditor and create a new Dummy Node. Call it "widgets_controller".Переключитесь на UnigineEditor и создайте новую Dummy Node. Назовите ее "widgets_controller".
- Assign the EndWidget property to the widgets_controller node.Присвойте свойство EndWidget ноде widgets_controller.
Step 2. Set Up the World WidgetШаг 2. Настройка глобального виджета#
The end widget provides button functionality and additional information about the current game outcome. To properly display the end widget, position it in front of the camera.Финальный виджет предоставляет функциональность кнопок и дополнительную информацию о текущем исходе игры. Чтобы правильно отобразить финальный виджет, расположите его перед камерой.
- Drag the programming_quick_start\ui\ui_plane.node from the Asset Browser to the world.Перетащите ноду programming_quick_start\ui\ui_plane.node из Asset Browser в мир.
-
Parent it to the PlayerDummy camera. Set the position of the ui_plane node to the following values.Привяжите ее к камере PlayerDummy. Установите положение ноды ui_plane на следующие значения.
- Save changes to the world, go to File->Save World or press Ctrl + S hotkey.Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
-
Next, we add a win condition check, end widget text, and a callback run to the LevelManager class. Let's also assign the UiElement component to the end widget's buttons right there in the LevelManager class.Далее мы добавляем в класс LevelManager проверку условия победы, текст финального виджета и запуск обратного вызова. Давайте также назначим компонент UiElement кнопкам финального виджета прямо здесь, в классе LevelManager.
Set the Ui Type to Restart, check the 6th bit option for the Ui Mask (matching the button's Intersection Mask) to correctly process selection intersections and set the Select Scale to 1.05 in order to make buttons bigger on cursor hover. Also, set the Select Emission to #505050 to make the button change its color.Установите значение Ui Type на Restart, включите 6-й бит для параметра Ui Mask (соответствующий маске Intersection кнопки), чтобы правильно обрабатывать пересечения выделения, и установите для параметра Select Scale значение 1.05, чтобы кнопки увеличивались при наведении курсора. Кроме того, установите для параметра Select значение #505050, чтобы кнопка изменила свой цвет.
To do so, open the LevelManager component in your IDE and replace it with the following code. Don't forget to save your code.Для этого откройте компонент 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); 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::get(); // 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 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) { endText->setText("Success!"); if (endGameEvent) endGameEvent->run(); isCounting = false; } } void LevelManager::shutdown() { widget_timer->deleteLater(); widget_goal->deleteLater(); } void LevelManager::decPhysicalObjectsNum() { physicalObjectsNum--; }
- Build and run the solution to regenerate a property file for the LevelManager component.Создайте и запустите решение для восстановления файла свойств для компонента LevelManager.
- Switch to the UnigineEditor, select the ui_plane node in the World Nodes window and right-click to choose Unpack To Node Content to link the buttons nodes to the LevelManager component.Переключитесь на UnigineEditor, выберите ноду ui_plane в окне World Nodes и щелкните правой кнопкой мыши и выберите Unpack To Node Content, чтобы связать ноды кнопок с компонентом LevelManager.
-
Then drag the buttons (restart_button and exit_button) to the corresponding fields of the LevelManager property.Затем перетащите кнопки (restart_button и exit_button) в соответствующие поля свойства LevelManager.
-
Drag the ui_plane node to the End Game Widget field under the EndWidget component of the widgets_controller node.Перетащите ноду ui_plane в поле End Game Widget под компонентом EndWidget ноды widgets_controller.
- Save changes to the world, go to File->Save World or press Ctrl + S hotkey.Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl + S.
- Switch to your IDE, then build and run the game in your IDE to see the UI in action.Переключитесь на свою IDE, затем создайте и запустите игру в своей IDE, чтобы увидеть пользовательский интерфейс в действии.