This page has been translated automatically.
Unigine Basics
1. Introduction
2. Managing Virtual Worlds
3. Preparing 3D Models
4. Materials
5. Cameras and Lighting
7. Making Cutscenes and Recording Videos
8. Preparing Your Project for Release
9. Physics
10. Optimization Basics
11. PROJECT2: First-Person Shooter
12. PROJECT3: Third-Person Cross-Country Arcade Racing Game
13. PROJECT4: VR Application With Simple Interaction

User Interface

In UNIGINE a Graphical User Interface (GUI) is composed of different types of widgets added to it. Basically, there are two ways of creating GUI:

  • By adding widgets to the system GUI (UNIGINE user interface) that is rendered on top of application window. In this case we use the Gui class.
  • By adding widgets to a GUI object positioned in the world. In this case, any postprocessing filter can be applied. By creating a specific Gui object (or GuiMesh for arbitrary geometry) that can be placed anywhere in the scene, and adding widgets to it. This can be useful, for example, to implement interaction with the interface displayed on a computer screen in a room (i.e. UI bound to an object that can be viewed from different angles). In this case, a post-processing filter can be applied.

There are 2 ways to create the GUI layout:

  • Directly from code via GUI classes
  • Using UI files with the description of user interface. Such a description file in XML-like format can be created for a complex interface to write less code.

The following code demonstrates how to add Label and Slider widgets to the system GUI:

Source code (C++)
// getting a reference to the system GUI
gui = Gui::getCurrent();

// creating a label widget and setting up its parameters
WidgetLabelPtr widget_label = WidgetLabel::create(gui, "Label text:");
widget_label->setToolTip("This is my label!");
widget_label->arrange();
widget_label->setPosition(10, 10);

// creating a slider widget and setting up its parameters
WidgetSliderPtr widget_slider = WidgetSlider::create(gui, 0, 360, 90);
widget_slider->setToolTip("This is my slider!");
widget_slider->arrange();
widget_slider->setPosition(100, 10);

// adding widgets to the system GUI
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);

In order to use GUI elements (not just watch them rendered) we must specify handlers for various events (click, change, etc.). The following code demonstrates how to set event handlers:

Source code (C++)
// EventConnections class instance to manage event subscriptions
EventConnections econnections;

/// function to be called when button1 is clicked
int onButton1Clicked(const WidgetPtr &button)
{
	/* .. */
}

/// method to be called when slider position is changed
int AppWorldLogic::onSliderChanged(const WidgetPtr &slider)
{
	/* .. */
}

int AppWorldLogic::init()
{
	// getting the reference to the system GUI
	GuiPtr gui = Gui::getCurrent();

	/* .. */


	// adding the widget to the system GUI
	gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
	// subscbribing for the clicked event with the onButton1Clicked function as a handler
	buttonwidget1->getEventClicked().connect(econnections, onButton1Clicked);

	// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
	widget_slider->getEventChanged().connect(econnections, this, &AppWorldLogic::onSliderChanged);
	/* .. */

	return 1;
}

int AppWorldLogic::shutdown()
{

	// removing all event subscriptions somewhere on shutdown
	econnections.disconnectAll();

	return 1;
}

Practice#

In architectural visualization projects, a widespread option is changing materials on various objects, for example, to preview different wallpapers on the walls and match them with the decor of the furniture. For our project, let's create a component (also inherit it from Interactable) that displays a list of available materials for a selected object in the UI based on ObjectGui with the ability to select and automatically apply them.

  1. Create a new component called MaterialCustomizer and add the following code to it:

    MaterialCustomizer.h
    #pragma once
    #include <UnigineComponentSystem.h>
    #include "Interactable.h"
    class MaterialCustomizer :
    	public Interactable
    {
    public:
    	// declare constructor and destructor for our class and define the name of the property to be associated with the component.
    	// The MaterialCustomizer.prop file containing all parameters listed below will be generated in your project's data folder after running the app for the first time
    	COMPONENT_DEFINE(MaterialCustomizer, Interactable);
    	// List of materials for object customization
    	PROP_ARRAY(Material, MaterialsList);
    	// registering methods to be called at the corresponding stages of the world logic (methods are declared in the protected-section below)
    	COMPONENT_INIT(init);
    
    	// Action method override for the child-component implementing the context menu
    	void action(int num = 0);
    protected:
    	// declaration of methods to be called at the corresponding stages of the world logic
    	void init();
    
    private:
    	// GUI object for displaying of the material selection interface
    	Unigine::ObjectGuiPtr gui = nullptr;
    	// the function that assigns the material selected in the menu to the selected customized object's surface
    	void select_material_handler(const Unigine::WidgetPtr& w);
    	// EventConnections class instance to manage event subscriptions
    	Unigine::EventConnections econn;
    };
    MaterialCustomizer.cpp
    #include "MaterialCustomizer.h"
    #include "InputProcessor.h"
    REGISTER_COMPONENT(MaterialCustomizer);
    using namespace Unigine;
    using namespace Math;
    
    // the function that assigns the material selected in the menu to the selected customized object's surface
    void MaterialCustomizer::select_material_handler(const WidgetPtr& w)
    {
    	Unigine::ObjectPtr object = checked_ptr_cast<Unigine::Object>(node);
    	Unigine::WidgetComboBoxPtr combo_box = checked_ptr_cast<Unigine::WidgetComboBox>(w);
    	object->setMaterial(MaterialsList[combo_box->getCurrentItem()], ComponentSystem::get()->getComponentInWorld<InputProcessor>()->intersection->getSurface());
    }
    
    void MaterialCustomizer::init()
    {
    	// setting the text of the tooltip that will be displayed when the cursor hovers over the object
    	tooltip = "Right-click displays a menu where you can select a material for the object.";
    
    	// creating the GUI object to display the material selection interface and setting its size and resolution
    	gui = ObjectGui::create(0.5f, 0.5f);
    	gui->setScreenSize(600, 600);
    
    	// enabling intersection detection to interact with UI using the mouse
    	gui->setIntersection(true, 0);
    
    	// making the interface always rotate to the user and not blocked by other objects
    	gui->setBillboard(true);
    	gui->getMaterialInherit(0)->setDepthTest(false);
    	// disabling the black background and temporarily hiding the UI
    	gui->setBackground(false);
    	gui->setEnabled(false);
    
    	// setting the distance from which interaction with the interface is possible
    	gui->setControlDistance(10);
    
    	if (MaterialsList.size() > 0)
    	{
    		// adding the WidgetVBox widget container for vertical layout of elements, setting indents
    		WidgetVBoxPtr vbox = WidgetVBox::create(gui->getGui());
    		vbox->setSpace(20, 20);
    
    		// enabling the background and setting its color
    		vbox->setBackground(1);
    		vbox->setBackgroundColor(vec4(0.1f, 0.5f, 0.1f, 0.4f));
    
    		// creating a WidgetLabel widget to display the header, setting its position, and font size
    		WidgetLabelPtr label = WidgetLabel::create(gui->getGui(), "Select Material:");
    		label->setFontSize(50);
    		// adding the Label widget to the container
    		vbox->addChild(label, Gui::ALIGN_TOP);
    
    		// creating a WidgetComboBox widget to display a drop-down list of materials, and setting its font size
    		WidgetComboBoxPtr mat_menu = WidgetComboBox::create(gui->getGui());
    		mat_menu->setFontSize(40);
    
    		// setting the handler on selection of an item from the list
    		mat_menu->getEventChanged().connect(econn, this, &MaterialCustomizer::select_material_handler);
    
    		// adding items to the menu according to the list of materials
    		for (int i = 0; i < MaterialsList.size(); i++)
    		{
    			Unigine::String str = FileSystem::getVirtualPath(MaterialsList[i].get()->getFilePath());
    			mat_menu->addItem(str.substr(str.rfind("/") + 1));
    		}
    		// adding the ComboBox widget to the container
    		vbox->addChild(mat_menu, Gui::ALIGN_TOP);
    
    		// adding container widget to GUI
    		gui->getGui()->addChild(vbox, Gui::ALIGN_EXPAND | Gui::ALIGN_OVERLAP);
    	}
    
    }
    
    // Action method override for the child-component
    void MaterialCustomizer::action(int num)
    {
    	// action indices different from zero are invalid for this component, so they are ignored
    	if (num != 0)
    		return;
    
    	// placing the GUI near the click point
    	gui->setWorldPosition(ComponentSystem::get()->getComponentInWorld<InputProcessor>()->intersection->getPoint());
    
    	// hide the GUI for all other customizable objects (if any)
    	Unigine::Vector<MaterialCustomizer*> customizer_components;
    	ComponentSystem::get()->getComponentsInWorld(customizer_components);
    	for (MaterialCustomizer* customizer : customizer_components)
    		if (this != customizer)
    			customizer->gui->setEnabled(false);
    
    	// show or hide the interface of material selection for the object
    	gui->setEnabled(!gui->isEnabled());
    }

    Let's save our files and then build and run our application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign our component to nodes. Close the application after running it and switch to UnigineEditor.

  2. Assign our new property to the bedside_table_1 object in our scene.
  3. Fill in the list of materials. To do this, change the number of items in the list from 0 to 3 and then drag three materials into the corresponding fields.

Upon right-clicking an object a list of available materials will be displayed, you can choose the desired one, and it will be applied automatically right as you click it:

Last update: 2024-11-06
Build: ()