12. Creating User Interface
<< RETURN TO THE PREVIOUS SECTION
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.
- By adding widgets to a GUI object positioned in the world. In this case, any postprocessing filter can be applied.
There are 2 ways to create the GUI layout:
- Directly from the code via GUI-related classes
- By using User interface (UI) files
#include <UnigineUserInterface.h>
using namespace Unigine;
GuiPtr gui;
int AppWorldLogic::init()
{
// getting a GUI pointer
gui = Gui::get();
// 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);
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
return 1;
}
In order to use GUI elements we must specify handlers for various events (click, change, etc.). The following code demonstrates how to set event handlers
#include <UnigineUserInterface.h>
using namespace Unigine;
GuiPtr gui;
/// function to be called when button1 is clicked
int onButton1Clicked()
{
/* .. */
}
/// method to be called when button2 is clicked
int AppWorldLogic::onButton2Clicked()
{
/* .. */
}
/// method to be called when delete button is clicked
int AppWorldLogic::onButtonDelClicked()
{
/* .. */
}
/// method to be called when slider position is changed
int AppWorldLogic::onSliderChanged()
{
/* .. */
}
int AppWorldLogic::init()
{
/* .. */
// getting a GUI pointer
gui = Gui::get();
// setting onButton1Clicked function as a clicked event handler for a buttonwidget1 using a CallbackBase variable
Unigine::CallbackBase *button1_clicked_callback_function = MakeCallback(onButton1Clicked);
buttonwidget1->addCallback(Gui::CLICKED, button1_clicked_callback_function);
// setting AppWorldLogic::onButton2Clicked method as a clicked event handler for a buttonwidget2 using a CallbackBase variable
Unigine::CallbackBase *button2_clicked_callback_method = MakeCallback(this, &AppWorldLogic::onButton2Clicked);
buttonwidget2->addCallback(Gui::CLICKED, button2_clicked_callback_method);
buttonwidget1->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonDelClicked));
// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
widget_slider->addCallback(Gui::CHANGED, MakeCallback(this, &AppWorldLogic::onSliderChanged));
/* .. */
}
Additional information:
- For more information on UI files, see the UI files article.
- For more information on GUI-related API, see the GUI-related classes page.
- For more information on the Gui class, see the Gui class article.
- For more information on handling events, see the Event Handling Callbacks article.
Project Progress#
In our project we are going to add the following widgets to the system GUI:
- Label (WidgetLabel) to display the name of the parameter controlled by the slider.
- Slider (WidgetSlider) to control the position of the sun manually.
- Checkbox (WidgetCheckbox) to enable/disable automatic change of the sun position.
- 4 Buttons (WidgetButton): Close Application, Load World, Load Mesh, and Delete.
- ComboBox (WidgetComboBox) to display the list of scene objects.
- File Dialog (WidgetDialogFile) to load a mesh from a file.
- Window (Widget) to set mesh parameters.
We are going to create all widgets directly from the code, but the last one (Mesh parameters window) we are going to load from a UI file. For this purpose let us create a file with the following contents and save it to the data folder of our application as "user_interface.ui"
<?xml version="1.0" encoding="utf-8"?>
<ui version="1.0">
<window name="window" export="1" space="4" sizeable="1" width="512">
<text>UserInterface</text>
<gridbox columns="2" space="8" align="expand">
<label name="label_n" align="expand">
<text>Name:</text>
</label>
<editline name="object_name" align="expand">
<text></text>
</editline>
<label name="label_x" align="expand">
<text>X coordinate:</text>
</label>
<editline name="coord_x" align="expand">
<text></text>
</editline>
<label name="label_y" align="expand">
<text>Y coordinate:</text>
</label>
<editline name="coord_y" align="expand">
<text></text>
</editline>
<label name="label_z" align="expand">
<text>Z coordinate:</text>
</label>
<editline name="coord_z" align="expand">
<text></text>
</editline>
</gridbox>
<button name="button_OK" align="bottom">
<text>OK</text>
</button>
</window>
</ui>
To create our GUI let us write event handler methods for the elements of our UI and a method called initGUI to initialize our UI.
In the AppWorldLogic.h file, we:
- include the UnigineUserInterface.h library,
- define smart pointers for the widgets we are going to use,
- declare event handlers for each widget and our initGUI method.
// AppWorldLogic.h
/* .. */
#include <UnigineUserInterface.h>
/* .. */
class AppWorldLogic : public Unigine::WorldLogic {
public:
/* .. */
// GUI event handlers
int onButtonDelClicked();
int onButtonCloseClicked();
int onButtonFopenClicked();
int onButtonWloadClicked();
int onDlgCancelClicked();
int onDlgOKClicked();
int onMeshParamsOKClicked();
int onSliderChanged();
/* .. */
private:
/* .. */
// creating GUI
int initGUI();
/* .. */
// pointers to the UI and its elements
Unigine::GuiPtr gui;
Unigine::UserInterfacePtr ui;
Unigine::WidgetLabelPtr widget_label;
Unigine::WidgetSliderPtr widget_slider;
Unigine::WidgetCheckBoxPtr widget_checkbox;
Unigine::WidgetButtonPtr widget_button_del;
Unigine::WidgetButtonPtr widget_button_close;
Unigine::WidgetButtonPtr widget_button_fopen;
Unigine::WidgetButtonPtr widget_button_wload;
Unigine::WidgetComboBoxPtr widget_combo;
Unigine::WidgetDialogFilePtr widget_file_dialog;
Unigine::WidgetPtr mesh_parameters_window;
/* .. */
};
In the AppWorldLogic.cpp file let us:
- implement all our event handlers,
- implement the initGUI method and insert it into the AppWorldLogic::init() method before initialization of objects (initObjects()),
- make some changes to the following methods in order to integrate our UI:
- updateLights (check if automatic mode is enabled to change sun position),
- addMeshToScene (add the name of the new created object to the combo box an enable Delete button if it was disabled),
- removeMeshFromScene (update the combo box and disable Delete button if the last object was deleted).
// AppWorldLogic.cpp
/* .. */
//-----------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------- GUI EVENT HANDLERS --------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Close button is clicked in the main window
int AppWorldLogic::onButtonCloseClicked()
{
// saving current world to my_world.world file
Console::run("world_save my_world");
// executing all pending console commands before exiting
Console::flush();
// closing the application
App::exit();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Load world button is clicked in the main window
int AppWorldLogic::onButtonWloadClicked()
{
// reporting to console
Log::warning("\n\n --- RELOADING A DEFAULT WORLD --- \n\n");
// loading a world from default.world file
World::loadWorld("default");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Cancel button is clicked in the main window
int AppWorldLogic::onButtonFopenClicked()
{
widget_file_dialog->setHidden(0);
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the slider position is changed in the main window
int AppWorldLogic::onSliderChanged()
{
// updating the position of the Sun
sun_angle = widget_slider->getValue();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Delete button is clicked in the main window
int AppWorldLogic::onButtonDelClicked()
{
String node_name;
if (widget_combo->getCurrentItem() == 0) // deleting all objects
{
removeObjects();
// disabling Delete button in the main window
widget_button_del->setEnabled(0);
}
else // deleting an object selected in the combo box
{
// retrieving currently selected node name in the combo box
node_name = widget_combo->getCurrentItemText();
// checking if a node with a given name exists and removing it from the scene
if (World::isNode(node_name))
{
removeMeshFromScene(node_name);
}
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Cancel button is clicked in the File Open dialog
int AppWorldLogic::onDlgCancelClicked()
{
// hiding the file selection dialog
widget_file_dialog->setHidden(1);
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the OK button is clicked in the File Open dialog
int AppWorldLogic::onDlgOKClicked()
{
// checking if the file exists
if (FILE *file = fopen(widget_file_dialog->getFile(), "r")) {
fclose(file);
//setting initial values of the fields in the Mesh Parameters window
String object_name = String::format("Object_%d", widget_combo->getNumItems() - 1);
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("object_name")))->setText(object_name.get());
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_x")))->setText("0.0");
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_y")))->setText("0.0");
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_z")))->setText("0.0");
// hiding the File Open dialog and showing Mesh Parameters window using different ways
widget_file_dialog->setHidden(1);
mesh_parameters_window->setHidden(0);
return 1;
}
// reporting an error to the console
Log::error("Loading mesh: can't open \"%s\" file\n", widget_file_dialog->getFile());
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the OK button is clicked in the Mesh Parameters window
int AppWorldLogic::onMeshParamsOKClicked()
{
//retrieving pointers to UI elements
WidgetEditLinePtr name = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("object_name")));
WidgetEditLinePtr x = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_x")));
WidgetEditLinePtr y = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_y")));
WidgetEditLinePtr z = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_z")));
if ((x->getText() != "") && (y->getText() != "") && (z->getText() != "") && (name->getText() != ""))
{
Math::Vec3 pos = Math::Vec3(atof(x->getText()), atof(y->getText()), atof(z->getText()));
String result = String((addMeshToScene(widget_file_dialog->getFile(), name->getText(), "my_mesh_base3", pos)) ? "Ok!" : "Error!");
// reporting progress to the console
Log::message("(%s) mesh load operation status: %s\n", name->getText(), result.get());
// hiding the Mesh Parameters window
mesh_parameters_window->setHidden(1);
return 1;
}
Log::error("Wrong mesh parameters!\n");
return 0;
}
/* .. */
/// method performing initialization of the GUI
int AppWorldLogic::initGUI()
{
// getting a GUI pointer
gui = Gui::get();
// creating a label widget and setting up its parameters
widget_label = WidgetLabel::create(gui, "Sun position:");
widget_label->setToolTip("Change sun position");
widget_label->setPosition(10, 10);
// creating a slider widget and setting up its parameters
widget_slider = WidgetSlider::create(gui, 0, 360, 90);
widget_slider->setToolTip("Change sun position");
widget_slider->setPosition(100, 10);
widget_slider->addCallback(Gui::CHANGED, MakeCallback(this, &AppWorldLogic::onSliderChanged));
// creating a checkbox widget and setting up its parameters
widget_checkbox = WidgetCheckBox::create(gui, "Automatic change");
widget_checkbox->setToolTip("Change sun position automatically");
widget_checkbox->setPosition(10, 30);
widget_checkbox->setChecked(1);
// creating a button widget and setting up its parameters
widget_button_del = WidgetButton::create(gui, "Delete");
widget_button_del->setToolTip("Delete object(s) selected in the combo box");
widget_button_del->setPosition(170, 50);
widget_button_del->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonDelClicked));
// creating a button widget and setting up its parameters
widget_button_fopen = WidgetButton::create(gui, "Load mesh");
widget_button_fopen->setToolTip("Load a mesh file");
widget_button_fopen->setPosition(10, 80);
widget_button_fopen->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonFopenClicked));
// creating a button widget and setting up its parameters
widget_button_close = WidgetButton::create(gui, "Quit");
widget_button_close->setToolTip("Close application");
widget_button_close->setPosition(10, 110);
widget_button_close->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonCloseClicked));
// creating a button widget and setting up its parameters
widget_button_wload = WidgetButton::create(gui, "Load world");
widget_button_wload->setToolTip("Load a default world");
widget_button_wload->setPosition(110, 110);
widget_button_wload->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonWloadClicked));
// creating a combo box widget and setting up its parameters
widget_combo = WidgetComboBox::create(gui);
widget_combo->addItem("----ALL-----");
widget_combo->setPosition(10, 50);
// creating a File Selection dialog widget and setting up its parameters
widget_file_dialog = WidgetDialogFile::create(gui, "Select a mesh file");
widget_file_dialog->setPath("../");
widget_file_dialog->setHidden(1);
widget_file_dialog->setFilter(".mesh");
widget_file_dialog->getCancelButton()->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onDlgCancelClicked));
widget_file_dialog->getOkButton()->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onDlgOKClicked));
// creating a Mesh Parameters dialog from an external ui-file
ui = UserInterface::create(gui, "user_interface.ui");
ui->addCallback("button_OK", Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onMeshParamsOKClicked));
mesh_parameters_window = ui->getWidget(ui->findWidget("window"));
mesh_parameters_window->setHidden(1);
// adding created widgets to the UI
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 0 - label
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 1 - slider
gui->addChild(widget_checkbox, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 2 - checkbox
gui->addChild(widget_button_del, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 3 - delete button
gui->addChild(widget_button_close, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 4 - close button
gui->addChild(widget_combo, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 5 - combo box
gui->addChild(widget_button_fopen, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 6 - Load Mesh button
gui->addChild(widget_button_wload, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 7 - Load world button
gui->addChild(mesh_parameters_window, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED | Gui::ALIGN_CENTER); // ID: 8 - Mesh Parameters window
gui->addChild(widget_file_dialog, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED | Gui::ALIGN_CENTER); // ID: 9 - File Open dialog
//reporting progress to the console
for (int k = 0; k < gui->getNumChildren() ; k++)
{
Log::message("-> Adding UI element %d: %s.\n", k, gui->getChild(k)->getTypeName());
}
Log::warning("UI initialized! Number of elements: %d.\n", gui->getNumChildren());
return 1;
}
/* .. */
int AppWorldLogic::init()
{
/* .. */
// creating GUI
initGUI();
/* .. */
return 1;
}
/* .. */
/// method updating positions of lights
int AppWorldLogic::updateLights(float ifps)
{
// updating the Sun position if automatic mode is enabled
if (widget_checkbox->isChecked())
{
sun_angle += SUN_ROTATION_RATE * ifps;
if (sun_angle > 360.0f) sun_angle = 0.0f;
// updating slider position
widget_slider->setValue(sun_angle);
}
// updating the Sun position
thesun->setWorldRotation(Math::quat(Math::vec3(0.0f, 1.0f, 0.0f), 180.0f - sun_angle));
return 1;
}
/* .. */
//-----------------------------------------------------------------------------------------------------------------------------
/// method adding a Dynamic Mesh Object to the scene. If an empty filename is passed the function creates a default box
int AppWorldLogic::addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position)
{
MeshPtr mesh = Mesh::create();
ObjectMeshDynamicPtr omd;
if (file_name){ // loading a mesh from a specified file
if (!mesh->load(file_name))
{
Log::error("\nError opening .mesh file!\n");
mesh.clear();
return 0;
}
else omd = ObjectMeshDynamic::create(mesh);
}
else //creating a default box
{
mesh->addBoxSurface("box_surface", Math::vec3(0.5f));
omd = ObjectMeshDynamic::create(mesh);
}
// setting object's material, name and transformation
omd->setMaterial(material_name, "*");
omd->setName(mesh_name);
omd->setWorldPosition(position);
// finding the "my_node_base" property
PropertyPtr my_node_base = Properties::findManualProperty("my_node_base");
// assigning the property to the node so a new internal child property is created and inherits all parameters of the parent one
omd->addProperty(my_node_base);
omd->getProperty(0)->getParameterPtr("material")->setValue(material_name);
omd->getProperty(0)->getParameterPtr("selected")->setValue(0);
// enabling intersection detection for the first surface (0)
omd->setIntersection(1, 0);
// updating combo list and the list of scene objects
widget_combo->addItem(omd->getName());
Objects.append(omd);
// enabling Delete button in the main window
widget_button_del->setEnabled(1);
// reporting progress to the console
Log::message("-> Object %s added to the scene.\n", mesh_name);
// clearing the mesh
mesh.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method deleting a Dynamic Mesh Object with a given name from the scene
int AppWorldLogic::removeMeshFromScene(const char *node_name)
{
// getting a pointer to the node with a given name and downcasting it to ObjectMeshDynamicPtr
ObjectMeshDynamicPtr object = checked_ptr_cast<ObjectMeshDynamic>(World::getNodeByName(node_name));
if (object)
{
// checking if the node to be removed was previously selected
if (old_selection && old_selection == object)
{
// resetting old_selection pointer to NULL
old_selection.clear();
}
// reporting node deletion to the console
Log::message("Removing %s node named %s from the scene.\n", object->getTypeName(), node_name);
// removing the node with a given name from the list of scene objects and updating the combobox
for (int i = 0; i < Objects.size();i++)
{
if (strcmp(Objects[i]->getName(), node_name) == 0)
{
Objects.remove(i);
widget_combo->removeItem(i + 1);
break;
}
}
// removing the node from the scene
object->deleteLater();
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
int AppWorldLogic::shutdown() {
// deleting all created nodes
removeObjects();
// clearing the player pointer
player.clear();
// clearing light sources
thesun.clear();
light_omni.clear();
projector.clear();
// clearing all created materials
clearMaterials();
// clearing gui elements
gui.clear();
// resetting old_selection pointer
old_selection.clear();
return 1;
}
/* .. */
Source Files
You can copy the code below and paste it to the corresponding source files of your project:
AppWorldLogic.h
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__
#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineGame.h>
#include <UnigineLights.h>
#include <UnigineMaterials.h>
#include <UnigineWorld.h>
#include <UnigineApp.h>
#include <UnigineConsole.h>
#include <UnigineInput.h>
#include <UnigineUserInterface.h>
using namespace Unigine;
// auxiliary constants
const float DELTA_ANGLE = 60.0f; // delta angle of objects rotation
const float MOVING_SPEED = 3.0f; // speed of objects movement
const float CHANGE_INTERVAL = 1.0f; // the interval between changes of objects' parameters, in seconds
const float SUN_ROTATION_RATE = 10.0f; // rotation rate of the sun
class AppWorldLogic : public WorldLogic {
public:
AppWorldLogic();
virtual ~AppWorldLogic();
virtual int init();
virtual int update();
virtual int postUpdate();
virtual int updatePhysics();
virtual int shutdown();
virtual int save(const StreamPtr &stream);
virtual int restore(const StreamPtr &stream);
// GUI event handlers
int onButtonDelClicked();
int onButtonCloseClicked();
int onButtonFopenClicked();
int onButtonWloadClicked();
int onDlgCancelClicked();
int onDlgOKClicked();
int onMeshParamsOKClicked();
int onSliderChanged();
private:
PlayerSpectatorPtr player;
// pointers to the GUI elements
GuiPtr gui;
UserInterfacePtr ui;
WidgetLabelPtr widget_label;
WidgetSliderPtr widget_slider;
WidgetCheckBoxPtr widget_checkbox;
WidgetButtonPtr widget_button_del;
WidgetButtonPtr widget_button_close;
WidgetButtonPtr widget_button_fopen;
WidgetButtonPtr widget_button_wload;
WidgetComboBoxPtr widget_combo;
WidgetDialogFilePtr widget_file_dialog;
WidgetPtr mesh_parameters_window;
// pointers to light sources
LightWorldPtr thesun;
LightOmniPtr light_omni;
LightProjPtr projector;
// auxiliary functions
int addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position);
int removeMeshFromScene(const char *node_name);
int transformNode(NodePtr node);
// initialization functions
int initObjects();
int initPlayer();
int initLights();
int initMaterials();
int initGUI();
// update functions
int updateLights();
int updateObjects();
// shutdown functions
int clearMaterials();
int removeObjects();
// scene objects vector
Vector <ObjectMeshDynamicPtr> Objects;
ObjectPtr old_selection; // pointer to previously selected object
Math::vec3 current_objects_scale = Math::vec3(1.0f); // current scaling vector for objects
Math::Vec3 forward_direction = Math::Vec3(0.0f, -1.0f, 0.0f); // current forward direction vector for objects
float elapsed_time = CHANGE_INTERVAL; // current time left to change current scale and forward direction of our objects
float sun_angle = 0.0f; // current sun position
};
#endif // __APP_WORLD_LOGIC_H__
AppWorldLogic.cpp
#include "AppWorldLogic.h"
// World logic, it takes effect only when the world is loaded.
// These methods are called right after corresponding world script's (UnigineScript) methods.
//-----------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------- GUI EVENT HANDLERS --------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Close button is clicked in the main window
int AppWorldLogic::onButtonCloseClicked()
{
// saving current world to my_world.world file
Console::run("world_save my_world");
// executing all pending console commands before exiting
Console::flush();
// closing the application
App::exit();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Load world button is clicked in the main window
int AppWorldLogic::onButtonWloadClicked()
{
// reporting to console
Log::warning("\n\n --- RELOADING A DEFAULT WORLD --- \n\n");
// loading a world from default.world file
Console::run("world_load quick_start");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Cancel button is clicked in the main window
int AppWorldLogic::onButtonFopenClicked()
{
widget_file_dialog->setHidden(0);
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the slider position is changed in the main window
int AppWorldLogic::onSliderChanged()
{
// updating the position of the Sun
sun_angle = widget_slider->getValue();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Delete button is clicked in the main window
int AppWorldLogic::onButtonDelClicked()
{
String node_name;
if (widget_combo->getCurrentItem() == 0) // deleting all objects
{
removeObjects();
// disabling Delete button in the main window
widget_button_del->setEnabled(0);
}
else // deleting an object selected in the combo box
{
// retrieving currently selected node name in the combo box
node_name = widget_combo->getCurrentItemText();
// checking if a node with a given name exists and removing it from the scene
if (World::isNode(node_name))
{
removeMeshFromScene(node_name);
}
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the Cancel button is clicked in the File Open dialog
int AppWorldLogic::onDlgCancelClicked()
{
// hiding the file selection dialog
widget_file_dialog->setHidden(1);
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the OK button is clicked in the File Open dialog
int AppWorldLogic::onDlgOKClicked()
{
// checking if the file exists
if (FILE *file = fopen(widget_file_dialog->getFile(), "r")) {
fclose(file);
//setting initial values of the fields in the Mesh Parameters window
String object_name = String::format("Object_%d", widget_combo->getNumItems() - 1);
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("object_name")))->setText(object_name);
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_x")))->setText("0.0");
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_y")))->setText("0.0");
checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_z")))->setText("0.0");
// hiding the File Open dialog and showing Mesh Parameters window using different ways
widget_file_dialog->setHidden(1);
mesh_parameters_window->setHidden(0);
return 1;
}
// reporting an error to the console
Log::error("Loading mesh: can't open \"%s\" file\n", widget_file_dialog->getFile());
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method to be called when the OK button is clicked in the Mesh Parameters window
int AppWorldLogic::onMeshParamsOKClicked()
{
//retrieving pointers to UI elements
WidgetEditLinePtr name = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("object_name")));
WidgetEditLinePtr x = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_x")));
WidgetEditLinePtr y = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_y")));
WidgetEditLinePtr z = checked_ptr_cast<WidgetEditLine>(ui->getWidget(ui->findWidget("coord_z")));
if ((x->getText() != "") && (y->getText() != "") && (z->getText() != "") && (name->getText() != ""))
{
Math::Vec3 pos = Math::Vec3(atof(x->getText()), atof(y->getText()), atof(z->getText()));
String result = String((addMeshToScene(widget_file_dialog->getFile(), name->getText(), "my_mesh_base3", pos)) ? "Ok!" : "Error!");
// reporting progress to the console
Log::message("(%s) mesh load operation status: %s\n", name->getText(), result);
// hiding the Mesh Parameters window
mesh_parameters_window->setHidden(1);
return 1;
}
Log::error("Wrong mesh parameters!\n");
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- AUXILIARY FUNCTIONS AND METHODS ----------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method adding a Dynamic Mesh Object to the scene. If an empty filename is passed the method creates a default box; otherwise, loads a mesh-file with a given name.
int AppWorldLogic::addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position)
{
MeshPtr mesh = Mesh::create();
ObjectMeshDynamicPtr omd;
if (file_name) { // loading a mesh from a specified file
if (!mesh->load(file_name))
{
Log::error("\nError opening .mesh file!\n");
mesh.clear();
return 0;
}
else omd = ObjectMeshDynamic::create(mesh);
}
else // creating a default box
{
mesh->addBoxSurface("box_surface", Math::vec3(0.5f));
omd = ObjectMeshDynamic::create(mesh);
}
// setting object's material, name and transformation
omd->setMaterial(material_name, "*");
omd->setName(mesh_name);
omd->setWorldPosition(position);
// finding the "my_node_base" property
PropertyPtr my_node_base = Properties::findManualProperty("my_node_base");
// assigning the property to the node so a new internal child property is created and inherits all parameters of the parent one
omd->addProperty(my_node_base);
PropertyPtr internal_property = omd->getProperty(0);
internal_property->setParameterString(internal_property->findParameter("material"), material_name);
internal_property->getParameterPtr("selected")->setValue(0);
// enabling intersection detection for the first surface (0)
omd->setIntersection(1, 0);
// updating combo list and the list of scene objects
widget_combo->addItem(omd->getName());
Objects.append(omd);
// enabling Delete button in the main window
widget_button_del->setEnabled(1);
// reporting progress to the console
Log::message("-> Object %s added to the scene.\n", mesh_name);
// clearing the mesh
mesh.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method deleting a Dynamic Mesh Object with a given name from the scene
int AppWorldLogic::removeMeshFromScene(const char *node_name)
{
// getting a pointer to the node with a given name and downcasting it to ObjectMeshDynamicPtr
ObjectMeshDynamicPtr object = checked_ptr_cast<ObjectMeshDynamic>(World::getNodeByName(node_name));
if (object)
{
// checking if the node to be removed was previously selected
if (old_selection && old_selection == object)
{
// resetting old_selection pointer to NULL
old_selection.clear();
}
// reporting node deletion to the console
Log::message("Removing %s node named %s from the scene.\n", object->getTypeName(), node_name);
// removing the node with a given name from the list of scene objects
for (int i = 0; i < Objects.size(); i++)
{
if (strcmp(Objects[i]->getName(), node_name) == 0) {
Objects.remove(i);
widget_combo->removeItem(i + 1);
break;
}
}
// removing the node from the scene
object->deleteLater();
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing node transformations
int AppWorldLogic::transformNode(NodePtr node)
{
// getting current node transformation matrix
Math::Mat4 transform = node->getTransform();
// calculating delta rotation around an arbitrary axis
Math::quat delta_rotation = Math::quat(rand() % 2, rand() % 2, rand() % 2, DELTA_ANGLE * Game::getIFps());
// setting node's scale, rotation and position
node->setWorldScale(current_objects_scale);
node->setWorldRotation(node->getWorldRotation() * delta_rotation);
node->setWorldPosition(node->getWorldPosition() + forward_direction * MOVING_SPEED * Game::getIFps());
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// function getting an object under the mouse cursor
ObjectPtr getObjectUnderMouseCursor(const PlayerPtr &player, int mouse_x, int mouse_y, float max_dist)
{
// setting start point (p0) at the center of player's screen
Math::Vec3 p0 = player->getWorldPosition();
// setting end point (p1) according to the position of the mouse cursor
Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromScreen(mouse_x, mouse_y)) * max_dist;
WorldIntersectionPtr intersection = WorldIntersection::create();
// returning the pointer to the first intersected object if any or NULL
return World::getIntersection(p0, p1, 1, intersection);
}
//-----------------------------------------------------------------------------------------------------------------------------
/// function setting new object as selected and updating previous selection
int selectObject(ObjectPtr new_selected_object, ObjectPtr &old_selected_object)
{
// checking if we already have the object selected previously
if (old_selected_object == new_selected_object)
return 0;
// checking if we already have a previously selected object and setting its "selected" parameter of the property to 0
if (old_selected_object)
{
int property_id = old_selected_object->findProperty("my_node_base");
if (property_id >= 0)
{
PropertyPtr my_node_base = old_selected_object->getProperty(property_id);
my_node_base->setParameterInt(my_node_base->findParameter("selected"), 0);
}
}
// checking if new selected object is not NULL and it has a property with parameter "selected" assigned
if (new_selected_object)
{
int property_id = new_selected_object->findProperty("my_node_base");
if (property_id >= 0)
{
PropertyPtr my_node_base = new_selected_object->getProperty(property_id);
my_node_base->setParameterInt(my_node_base->findParameter("selected"), 1);
old_selected_object = new_selected_object;
}
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- INITIALIZATION METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the set of 4 boxes
int AppWorldLogic::initObjects()
{
int index = 0;
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
addMeshToScene(NULL, String::format("my_meshdynamic_%d", index), String::format("my_mesh_base%d", index), Math::Vec3(x, y, 1.0f));
index++;
}
}
// reporting progress to the console
Log::warning("Objects generation OK!\n\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the player
int AppWorldLogic::initPlayer()
{
// creating a new PlayerSpectator instance
player = PlayerSpectator::create();
// setting player's FOV, ZNear, ZFar
player->setFov(90.0f);
player->setZNear(0.1f);
player->setZFar(10000.0f);
// setting player's view direction vector and position
player->setPosition(Math::Vec3(3.0f));
player->setDirection(Math::vec3(-1.0f), Math::vec3(0.0f, 0.0f, -1.0f));
// setting the player as a default one via the Game singleton instance
Game::setPlayer(player);
//reporting progress to the console
Log::warning("\nPlayer initialization OK!\n\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of lights
int AppWorldLogic::initLights()
{
// creating an omni light and setting up its parameters
light_omni = LightOmni::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f), 10.0f, "");
light_omni->setWorldPosition(Math::Vec3(0.0f, 0.0f, 5.0f));
light_omni->setIntensity(0.1f);
// reporting progress to the console
Log::message("-> Created a %s light source.\n", light_omni->getName());
// creating a world light and setting up its parameters
thesun = LightWorld::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f));
thesun->setName("Sun");
thesun->setDisableAngle(90.0f);
thesun->setIntensity(1.0f);
thesun->setScattering(LightWorld::SCATTERING_SUN);
thesun->setWorldRotation(Math::quat(86.0f, 30.0f, 300.0f));
// reporting progress to the console
Log::message("-> Created a %s light source.\n", thesun->getName());
// creating a proj light and setting up its parameters
projector = LightProj::create(Math::vec4(1.0f, 1.0f, 0.5f, 1.0f), 10.0f, 60.0f, "");
projector->setWorldPosition(Math::Vec3(2.5f, 2.5f, 3.0f));
projector->setName("projector");
projector->setRotation(Math::quat(-45.0f, 45.0f, 0.0f));
projector->setPenumbra(0.425f);
projector->setIntensity(1.0f);
// reporting progress to the console
Log::message("-> Created a %s light source.\n", projector->getName());
Log::warning("Lights initialization OK!\n");
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of materials
int AppWorldLogic::initMaterials()
{
// creating a new child material of the mesh_base and setting its color
MaterialPtr mesh_base = Materials::findMaterial("mesh_base");
MaterialPtr my_mesh_base = mesh_base->inherit("my_mesh_base0");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 0, 0, 255));
// reporting progress to the console
Log::message("\n-> Generated %s material.\n", my_mesh_base->getName());
// creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base1");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(0, 255, 0, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
//creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base2");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(0, 0, 255, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
//creating a new child material of the mesh_base and setting its color
my_mesh_base = mesh_base->inherit("my_mesh_base3");
my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 255, 0, 255));
// reporting progress to the console
Log::message("-> Generated %s material.\n", my_mesh_base->getName());
Log::warning("Material generation OK!\n\n");
// clearing material pointer
my_mesh_base.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the GUI
int AppWorldLogic::initGUI()
{
// getting a GUI pointer
gui = Gui::get();
// creating a label widget and setting up its parameters
widget_label = WidgetLabel::create(gui, "Sun position:");
widget_label->setToolTip("Change sun position");
widget_label->setPosition(10, 10);
// creating a slider widget and setting up its parameters
widget_slider = WidgetSlider::create(gui, 0, 360, 90);
widget_slider->setToolTip("Change sun position");
widget_slider->setPosition(100, 10);
widget_slider->addCallback(Gui::CHANGED, MakeCallback(this, &AppWorldLogic::onSliderChanged));
// creating a checkbox widget and setting up its parameters
widget_checkbox = WidgetCheckBox::create(gui, "Automatic change");
widget_checkbox->setToolTip("Change sun position automatically");
widget_checkbox->setPosition(10, 30);
widget_checkbox->setChecked(1);
// creating a button widget and setting up its parameters
widget_button_del = WidgetButton::create(gui, "Delete");
widget_button_del->setToolTip("Delete object(s) selected in the combo box");
widget_button_del->setPosition(170, 50);
widget_button_del->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonDelClicked));
// creating a button widget and setting up its parameters
widget_button_fopen = WidgetButton::create(gui, "Load mesh");
widget_button_fopen->setToolTip("Load a mesh file");
widget_button_fopen->setPosition(10, 80);
widget_button_fopen->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonFopenClicked));
// creating a button widget and setting up its parameters
widget_button_close = WidgetButton::create(gui, "Quit");
widget_button_close->setToolTip("Close application");
widget_button_close->setPosition(10, 110);
widget_button_close->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonCloseClicked));
// creating a button widget and setting up its parameters
widget_button_wload = WidgetButton::create(gui, "Load world");
widget_button_wload->setToolTip("Load a default world");
widget_button_wload->setPosition(110, 110);
widget_button_wload->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onButtonWloadClicked));
// creating a combo box widget and setting up its parameters
widget_combo = WidgetComboBox::create(gui);
widget_combo->addItem("----ALL-----");
widget_combo->setPosition(10, 50);
// creating a File Selection dialog widget and setting up its parameters
widget_file_dialog = WidgetDialogFile::create(gui, "Select a mesh file");
widget_file_dialog->setPath("../");
widget_file_dialog->setHidden(1);
widget_file_dialog->setFilter(".mesh");
widget_file_dialog->getCancelButton()->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onDlgCancelClicked));
widget_file_dialog->getOkButton()->addCallback(Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onDlgOKClicked));
// creating a Mesh Parameters dialog from an external ui-file
ui = UserInterface::create(gui, "user_interface.ui");
ui->addCallback("button_OK", Gui::CLICKED, MakeCallback(this, &AppWorldLogic::onMeshParamsOKClicked));
mesh_parameters_window = ui->getWidget(ui->findWidget("window"));
mesh_parameters_window->setHidden(1);
// adding created widgets to the UI
gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 0 - label
gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 1 - slider
gui->addChild(widget_checkbox, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 2 - checkbox
gui->addChild(widget_button_del, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 3 - delete button
gui->addChild(widget_button_close, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 4 - close button
gui->addChild(widget_combo, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 5 - combo box
gui->addChild(widget_button_fopen, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 6 - Load Mesh button
gui->addChild(widget_button_wload, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED); // ID: 7 - Load world button
gui->addChild(mesh_parameters_window, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED | Gui::ALIGN_CENTER);// ID: 8 - Mesh Parameters window
gui->addChild(widget_file_dialog, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED | Gui::ALIGN_CENTER); // ID: 9 - File Open dialog
//reporting progress to the console
for (int k = 0; k < gui->getNumChildren(); k++)
{
Log::message("-> Adding UI element %d: %s.\n", k, gui->getChild(k)->getTypeName());
}
Log::warning("UI initialized! Number of elements: %d.\n", gui->getNumChildren());
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- UPDATE METHODS ---------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the position of the sun
int AppWorldLogic::updateLights()
{
// updating the Sun rotation angle
if (widget_checkbox->isChecked()) {
sun_angle += SUN_ROTATION_RATE * Game::getIFps();
if (sun_angle > 360.0f) sun_angle = 0.0f;
// updating slider position
widget_slider->setValue(sun_angle);
}
// changing the Sun position using the new angle
thesun->setWorldRotation(Math::quat(Math::vec3(0.5f, 0.0f, 0.5f), 180.0f - sun_angle));
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the initial set of scene objects
int AppWorldLogic::updateObjects()
{
ObjectMeshDynamicPtr object;
// changing transformation for all scene objects named "my_meshdynamic_*" (initial set)
for (auto it = Objects.begin(); it != Objects.end(); it++)
{
object = it.get();
// searching for the property assigned to the object
int property_id = object->findProperty("my_node_base");
// checking if such property exists and has a "selected" parameter
if (property_id >= 0)
{
PropertyPtr p = object->getProperty(property_id);
if (p->getParameterPtr("selected")->getValueInt() == 1)
{
// if "selected" parameter value is 1 assigning "mesh_base" material to the object to highlight it
object->setMaterial("mesh_base", "*");
}
else
{
// if "selected" parameter value is 0 assigning the material that is stored in the "material" parameter of its property
object->setMaterial(p->getParameterString(p->findParameter("material")), "*");
}
}
// if a node is named "my_meshdynamic_*" (it belongs to the initial set) change its transformation
if (strstr(object->getName(), "my_meshdynamic_"))
{
// transform the node
transformNode(object);
}
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- SHUTDOWN METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created materials
int AppWorldLogic::clearMaterials()
{
Materials::removeMaterial(Materials::findMaterial("my_mesh_base0")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base1")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base2")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base3")->getGUID());
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created objects
int AppWorldLogic::removeObjects()
{
while (Objects.size() > 0)
{
removeMeshFromScene(Objects.begin()->get()->getName());
}
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
AppWorldLogic::AppWorldLogic() {
}
AppWorldLogic::~AppWorldLogic() {
}
int AppWorldLogic::init() {
// Write here code to be called on world initialization: initialize resources for your world scene during the world start.
// initializing pseudo-random number generator
Game::setSeed(time(NULL));
// creating materials
initMaterials();
// creating GUI
initGUI();
// creating objects
initObjects();
// creating a player
initPlayer();
// creating lights
initLights();
return 1;
}
// start of the main loop
int AppWorldLogic::update() {
// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.
// checking if it's time to change current scale and forward vector direction of our objects
if (elapsed_time < 0.0f)
{
// change current scaling vector for objects
current_objects_scale = Math::vec3(Game::getRandomFloat(0.8f, 1.2f));
// change forward direction for objects
forward_direction = forward_direction * Math::rotateZ(60.0f);
// resetting elapsed time counter
elapsed_time = CHANGE_INTERVAL;
}
// decreasing the time counter to the next change of current scale and forward vector direction
elapsed_time -= Game::getIFps();
// if right mouse button is clicked
if (Input::isMouseButtonPressed(Input::MOUSE_BUTTON_RIGHT))
{
// get and select object under the mouse cursor
Math::ivec2 mouse = Input::getMouseCoord();
selectObject(getObjectUnderMouseCursor(Game::getPlayer(), mouse.x, mouse.y, 100.0f), old_selection);
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (Input::isKeyDown(Input::KEY_Q) && !Console::isActive())
{
App::exit();
}
// updating objects
updateObjects();
// updating lights
updateLights();
return 1;
}
int AppWorldLogic::postUpdate() {
// The engine calls this function before rendering each render frame: correct behavior after the state of the node has been updated.
return 1;
}
int AppWorldLogic::updatePhysics() {
// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
return 1;
}
// end of the main loop
int AppWorldLogic::shutdown() {
// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.
// deleting all created nodes
removeObjects();
// clearing the player pointer
player->deleteLater();
// clearing light sources
thesun->deleteLater();
light_omni->deleteLater();
projector->deleteLater();
// clearing all created materials
clearMaterials();
// clearing gui elements
gui.clear();
// resetting old_selection pointer
old_selection.clear();
return 1;
}
int AppWorldLogic::save(const StreamPtr &stream) {
// Write here code to be called when the world is saving its state (i.e. state_save is called): save custom user data to a file.
UNIGINE_UNUSED(stream);
return 1;
}
int AppWorldLogic::restore(const StreamPtr &stream) {
// Write here code to be called when the world is restoring its state (i.e. state_restore is called): restore custom user data to a file here.
UNIGINE_UNUSED(stream);
return 1;
}
Congratulations!
Well, now our project is ready! You have done a great job and learned how to use basic functions of the Unigine Engine. If you need more detailed information on how to use Unigine C++ API please refer to our extended API manual.