Создание пользовательского интерфейса
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. Open the UiElement component in your IDE and copy the code below. Don't forget to save your code.Создайте новый компонент C# и назовите его UiElement. Этот компонент обрабатывает вызовы функций выбора и кнопок. Откройте компонент UiElement в вашей IDE и скопируйте приведенный ниже код. Не забудьте сохранить свой код.
UiElement.cs UIElement.cs
using System; using System.Collections; using System.Collections.Generic; using Unigine; #if UNIGINE_DOUBLE using Vec3 = Unigine.dvec3; #else using Vec3 = Unigine.vec3; #endif [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component public class UiElement : Component { [ShowInEditor][Parameter(Tooltip = "Type of UI element")] private Element uiType = Element.None; [ShowInEditor][ParameterMask(Tooltip = "Mask for detecting mouse intersection")] private int uiMask = 1; [ShowInEditor][Parameter(Tooltip = "Scale on hover")] private float selectScale = 1; [ShowInEditor][ParameterColor(Tooltip = "Emission color on hover")] private vec4 selectEmission = vec4.ONE; static public event Action<Element> onClick; public enum Element { Restart, Exit, None } public uint Id { get; private set; } static public bool AnyIsSelect { get { if (selectedObjects == 0) return false; return true; } } // ID of UI element static private uint idCount = 0; // count of selected objects static private int selectedObjects = 0; private WorldIntersection intersection = null; private bool isSelect = false; Unigine.Object uiObject = null; private vec3 sourceScale = vec3.ONE; private vec4 sourceEmission = vec4.ONE; void Init() { // set ID Id = idCount; ++idCount; selectedObjects = 0; // get the UI element uiObject = node as Unigine.Object; // remember the source scale and emission color sourceScale = node.Scale; sourceEmission = uiObject.GetMaterialParameterFloat4("emission_color", 0); intersection = new WorldIntersection(); } protected override void OnDisable() { // deselect an object if (isSelect) { --selectedObjects; if (selectedObjects < 0) selectedObjects = 0; isSelect = false; OnLeave(); } } void Update() { // getting direction from the current mouse position ivec2 mouse = Input.MousePosition; vec3 dir = Game.Player.GetDirectionFromMainWindow(mouse.x, mouse.y); // get points for intersection Vec3 p0 = Game.Player.WorldPosition; Vec3 p1 = p0 + dir * 25.0f; // find the intersection Unigine.Object obj = World.GetIntersection(p1, p0, uiMask, intersection); if (obj != null) { // try to get the UI element component and select/deselect it UiElement uiElement = ComponentSystem.GetComponent<UiElement>(obj); if (uiElement != null && uiElement.Id == Id) { if (!isSelect) { OnEnter(); isSelect = true; ++selectedObjects; } } else if (isSelect) { OnLeave(); isSelect = false; --selectedObjects; } } else { if (isSelect) { OnLeave(); isSelect = false; --selectedObjects; } } // invoke the mouse click event if (isSelect && Input.IsMouseButtonDown(Input.MOUSE_BUTTON.LEFT)) OnClick(); } private void OnEnter() { // set a visual effect on selection node.Scale = sourceScale * selectScale; uiObject.SetMaterialParameterFloat4("emission_color", selectEmission, 0); } private void OnLeave() { // remove a visual effect when the UI element is not selected anymore node.Scale = sourceScale; uiObject.SetMaterialParameterFloat4("emission_color", sourceEmission, 0); } private void OnClick() { onClick?.Invoke(uiType); } }
-
Next, add a win condition check, end widget text, and a delegate call to the LevelManager class. To do so, open the LevelManager component in an IDE and add the following code. Save your code in an IDE to ensure it's automatic compilation on switching back to UnigineEditor.Затем добавьте в LevelManager класс проверку условия победы, текст финального виджета и вызов делегата. Для этого откройте компонент LevelManager в среде IDE и добавьте следующий код. Сохраните свой код в среде IDE, чтобы обеспечить его автоматическую компиляцию при обратном переключении на UnigineEditor.
using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component public class LevelManager : Component { //========================== NEW - BEGIN =============================== public event Action endGameEvent; ObjectText endText; //=========================== NEW - END ================================ // level timer public float timer = 100.0f; bool isCounting = true; int physicalObjectsNum; WidgetLabel widget_timer, widget_goal; void Init() { InitGUI(); //========================== NEW - BEGIN =============================== // find the object text node of the widget endText = Game.Player.FindNode("header_text", 1) as ObjectText; //=========================== NEW - END ================================ // count physical objects in the level physicalObjectsNum = node.NumChildren; } void InitGUI() { // getting a GUI pointer Gui gui = Gui.GetCurrent(); // creating a label widget and setting up its parameters widget_timer = new WidgetLabel(gui, "Time Left:"); widget_timer.SetPosition(10, 10); widget_timer.FontColor = vec4.RED; widget_goal = new WidgetLabel(gui, "Objects Left: "); widget_goal.SetPosition(10, 30); widget_goal.FontColor = vec4.BLUE; // add widgets to the GUI gui.AddChild(widget_timer, Gui.ALIGN_OVERLAP); gui.AddChild(widget_goal, Gui.ALIGN_OVERLAP); } void Update() { // decrease the timer if (isCounting) { timer -= Game.IFps; if (timer <= 0) { //end game //========================== NEW - BEGIN ============================= endText.Text = "Game Over"; endGameEvent?.Invoke(); //========================== NEW - END =============================== isCounting = false; } } //win if (physicalObjectsNum <= 0) { //========================== NEW - BEGIN ============================= endText.Text = "Success!"; endGameEvent?.Invoke(); //========================== NEW - END =============================== isCounting = false; } // show the current time and objects left to clear if (isCounting) { widget_timer.Text = "Time Left: " + timer.ToString("0.0") + " s"; widget_goal.Text = "Objects Left: " + physicalObjectsNum.ToString(); } //hide the widgets on end game else { widget_timer.Enabled = false; widget_goal.Enabled = false; } } void Shutdown() { widget_timer.DeleteLater(); widget_goal.DeleteLater(); } public void DecPhysicalObjectsNum() { physicalObjectsNum--; } }
-
Create a new C# component and call it EndWidget. Open the component in your IDE and copy the code below. Save your code in an IDE to ensure it's automatic compilation on switching back to UnigineEditor.Создайте новый компонент C# и назовите его EndWidget. Откройте компонент в вашей IDE и скопируйте приведенный ниже код. Сохраните свой код в IDE, чтобы обеспечить его автоматическую компиляцию при возврате к UnigineEditor.
using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component public class EndWidget : Component { // object with the end game message public Node endGameWidget; LevelManager levelManager; void Init() { // set the end game event handler levelManager = ComponentSystem.FindComponentInWorld<LevelManager>(); if (levelManager != null) { levelManager.endGameEvent += EndGameEventHandler; } // set the mouse click handler for UI elements (Restart/Exit) UiElement.onClick += OnClickHandler; // hide the end UI endGameWidget.Enabled = false; } void EndGameEventHandler() { // set gui and input Input.MouseHandle = Input.MOUSE_HANDLE.USER; // show the end UI endGameWidget.Enabled = true; } void 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.Quit(); } void Shutdown() { // remove handlers if (levelManager != null) levelManager.endGameEvent -= EndGameEventHandler; UiElement.onClick -= OnClickHandler; } }
- Switch to the UnigineEditor, create a new Dummy Node, call it "widgets_controller" and assign the EndWidget component to it.Переключитесь на UnigineEditor, создайте новую Dummy Node, назовите ее "widgets_controller" и назначьте ей компонент EndWidget.
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 на следующие значения.
-
Click the Edit for the ui_plane NodeReference and expand the hierarchy.Нажмите Edit в Node Reference ui_plane и разверните иерархию.
- Assign a UiElement component to the child restart_button node.Назначьте компонент UiElement дочерней ноде restart_button.
-
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, чтобы кнопка изменила свой цвет.
- Set up a new UiElement component the same way for the exit_button, but set the Ui Type to Exit. Настройте новый компонент UiElement таким же образом для exit_button, но для Ui Type установите значение Exit.
- Confirm the node reference editing by clicking Apply in the Parameters window of the ui_plane node.Подтвердите редактирование Node Reference, нажав Apply в окне параметров ноды ui_plane.
-
Assign 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. Build and run the game to see the UI in action.Сохраните изменения в мире, перейдите к File->Save World или нажмите горячую клавишу Ctrl+S. Создайте и запустите игру, чтобы увидеть пользовательский интерфейс в действии.