UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
Programming
Setting Up Development Environment
Usage Examples
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

Accessing Nodes and Files via Properties

Every resource (asset) used in your project, be it a node, a mesh, a material, a texture, or any other, has a unique identifier (GUID). A GUID identifies a path to the asset (i.e., location of the asset in the project). GUIDs are used to keep all links and dependencies between the assets, regardless of their name and location within the project.

Using GUIDs to link your assets is safer than using file names, as you don’t have to worry, that your material will lose a texture when you change its name. However, managing GUIDs directly is rather confusing.

Property is a convenient way to link certain assets to a node via GUIDs without even thinking about them, giving you easy access to these assets. There are a number of property parameter types making it possible to do so:

  • node - for nodes
  • material - for materials
  • property - for properties
  • file - for all other files (textures, meshes, sounds, etc.)

Artists and programmers developing your project, should be able to work independently: artists prepare content (textures, materials, models etc.), while programmers write code implementing logic, that performs certain operations with this content.

Using properties makes the whole process simpler and more convenient:

  1. Artists can safely move or rename files and nodes. Programmers always work with properties: create them, set and read parameter values (which can represent links to various assets). Artists can set property parameters too, they do it via the Editor.
  2. Neither artists nor programmers should work with node IDs or GUIDs and remember them. Programmer always has at hand a variable (property parameter), that gives access to any necessary node or a file.

General property-based workflow is clear and simple. There are two basic cases, depending on the logic of your project:

General Workflow#

The general workflow for all projects, that don't use the Custom Component System should be as follows:

  1. First, we create a property to store links to all nodes and assets that we need and save it to our project's data folder. For example, the property can be like this:
    Source code (XML)
    <?xml version="1.0" encoding="utf - 8"?>
    <property version="2.7.3" name="my_property" parent_name="node_base" manual="1">
    	<parameter name="some_float" type="float">30.5</parameter>
    	<parameter name="some_string" type="string">Hello from my_property!</parameter>
    	<parameter name="some_node" type="node">0</parameter>
    	<parameter name="some_material" type="material"></parameter>
    	<parameter name="some_mesh" type="file"></parameter>
    	<parameter name="some_file" type="file"></parameter>
    </property>
  2. Then open the UnigineEditor, select the desired node, click Add new property and drag the property file to the new property field, then drag all necessary assets and nodes to the corresponding fields of the property (see the video below).

    Linking nodes and assets to the property.
  3. As we don't use components, we’ll have to be bound to names of nodes to which the properties with links to assets are assigned. So, in the WorldLogic's init() method we get a node by its name:
    Source code (C++)
    int AppWorldLogic::init() 
    {
    	/* ... */
    
    	NodePtr node = Editor::get()->getNodeByName("node_name");
    	
    	/* ... */
    
    	return 1;
    }
  4. Then we get a property assigned to it:
    Source code (C++)
    PropertyPtr property = node->getProperty();
  5. Now we can use the property to get access to nodes and files:
    • to get a node we can simply use the corresponding node parameter:
      Source code (C++)
      property->getParameterNode(property->findParameter("node_param_name"));
    • to get a path to file, we can simply use:
      Source code (C++)
      const char *path = property->getParameterFile(property->findParameter("file_param_name"));
      As we have a path to our file, we can use it, for example:
      Source code (C++)
      // to create a node reference
      NodeReferencePtr node_ref = NodeReference::create(path_to_node_file);
      
      // to load a sound source
      SoundSourcePtr sound = SoundSource::create(path_to_sound_file);

Let us use an example to illustrate this workflow.

Usage Example#

In this example we are going to manipulate nodes and assets linked to certain node using a property via C++, C# and UnigineScript.

Let us create a simple MeshStatic object named my_object, inherit a material from the mesh_base to assign to the surfaces of our object, and add some audio file (.mp3 or .oga) to our project.

So, we link a *.mesh file, a material, the material_ball node from the default world and an audio file using the property file described above.

In our code we will:

  • rotate the linked node
  • modify linked material and mesh and save changes
  • play a linked audio file

C++ Implementation#

Below you'll find the C++ implementation of the example described above. You can copy and paste it to the AppWorldLogic.cpp file of your project.

AppWorldLogic.cpp

Source code (C++)
#include "AppWorldLogic.h"
#include <UnigineMaterials.h>
#include <UnigineSounds.h>
#include <UnigineGame.h>
#include <UnigineEditor.h>
#include <UnigineFileSystem.h>

// World logic, it takes effect only when the world is loaded.
// These methods are called right after corresponding world script's (UnigineScript) methods.


using namespace Unigine;

NodePtr my_node;               // node to which a property with links is assigned
PropertyPtr property;          // property with all necessary links
MaterialPtr material;          // linked material
NodePtr param_node;            // linked node 
SoundSourcePtr sound;		   // sound source to be played 
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.
	// getting the node to which a property with links to assets is assigned 
	my_node = Editor::get()->getNodeByName("my_object");

	// getting the property, that will be used to acces all necessary files
	property = my_node->getProperty();

	// getting a material from the corresponding property parameter
	material = property->getParameterMaterial(property->findParameter("some_material"));

	// getting the path to the mesh file from the corresponding property parameter
	const char *mesh_file_name = property->getParameterFile(property->findParameter("some_mesh"));

	//  creating and modifying a mesh by adding a box surface to it
	MeshPtr mesh = Mesh::create(mesh_file_name);
	mesh->addBoxSurface("box", Math::vec3(0.3f, 0.3f, 2.0f));

	// saving the modified mesh back to the file
	mesh->save(mesh_file_name);

	// getting the path to the sound file from the corresponding property parameter
	const char *sound_file_name = property->getParameterFile(property->findParameter("some_file"));

	// getting a node from the corresponding property parameter
	param_node = property->getParameterNode(property->findParameter("some_node"));

	// creating and playing a sound from the file
	sound = SoundSource::create(sound_file_name);
	sound->setMaxDistance(100.0f);
	sound->setLoop(1);
	sound->play();

	// reporting results to the console
	Log::message("Path to mesh file: %s\nPath to sound file: %s\nNode ID: %d\n", mesh_file_name, sound_file_name, param_node->getID());
	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.

	float ifps = Game::get()->getIFps();

	// changing the material
	material->setParameter("albedo_color", Math::vec4(Game::get()->getRandomFloat(0.0f, 1.0f), Game::get()->getRandomFloat(0.0f, 1.0f), Game::get()->getRandomFloat(0.0f, 1.0f), 1.0f));

	// rotate linked node
	param_node->setRotation(param_node->getRotation() * Math::quat(0, 0, 30.0f * ifps));

	return 1;
}

int AppWorldLogic::render() {
	// 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::flush() {
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls flush() 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.
	// saving current material color (check it in the UnigineEditor to see that it was modified)
	material->save();
	return 1;
}

int AppWorldLogic::destroy() {
	// Write here code to be called when the video mode is changed or the application is restarted (i.e. video_restart is called). It is used to reinitialize the graphics context.
	
	return 1;
}

int AppWorldLogic::save(const Unigine::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 Unigine::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;
}

C# Implementation#

Below you'll find the C# implementation of the example described above. You can copy and paste it to the AppWorldLogic.cs file of your project.

AppWorldLogic.cs

Source code (C#)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

namespace UnigineApp
{
	class AppWorldLogic : WorldLogic
	{
		// World logic, it takes effect only when the world is loaded.
		// These methods are called right after corresponding world script's (UnigineScript) methods.

		Node my_node;			// node to which a property with links is assigned
		Property property;		// property with all necessary links
		Material material;		// linked material
		Node param_node;		// linked node
		SoundSource sound;		// sound source to be played 

		public AppWorldLogic()
		{
		}

		public override int init()
		{
			// Write here code to be called on world initialization: initialize resources for your world scene during the world start.

			// getting the node to which a property with links to assets is assigned 
			my_node = Editor.get().getNodeByName("my_object");

			// getting the property, that will be used to acces all necessary files
			property = my_node.getProperty();

			// getting a material from the corresponding property parameter
			material = property.getParameterMaterial(property.findParameter("some_material"));

			// modifying the material
			material.setParameter("albedo_color", new vec4(Game.get().getRandomFloat(0.0f, 1.0f), Game.get().getRandomFloat(0.0f, 1.0f), Game.get().getRandomFloat(0.0f, 1.0f), 1.0f));

			//saving modified material
			material.save();

			// getting the path to the mesh file from the corresponding property parameter
			String mesh_file_name = property.getParameterFile(property.findParameter("some_mesh"));

			//  creating and modifying a mesh by adding a box surface to it
			Mesh mesh = new Mesh(mesh_file_name);
			mesh.addBoxSurface("box", new vec3(0.3f, 0.3f,2.0f));

			// saving the modified mesh back to the file
			mesh.save(mesh_file_name);

			// getting the path to the sound file from the corresponding property parameter
			String sound_file_name = property.getParameterFile(property.findParameter("some_file"));

			// getting a node from the corresponding property parameter
			param_node = property.getParameterNode(property.findParameter("some_node"));

			// creating and playing a sound from the file
			sound = new SoundSource(sound_file_name);
			sound.setMaxDistance(100.0f);
			sound.setLoop(1);
			sound.play();

			// reporting results to the console
			Log.message("Path to mesh file: {0}\nPath to sound file: {1}\nNode ID: {2}\n", mesh_file_name, sound_file_name, param_node.getID());

			return 1;
		}

		// start of the main loop
		public override int 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.
			float ifps = Game.get().getIFps();

			// changing the material
			 material.setParameter("albedo_color", new vec4(Game.get().getRandomFloat(0.0f, 1.0f), Game.get().getRandomFloat(0.0f, 1.0f), Game.get().getRandomFloat(0.0f, 1.0f), 1.0f));
	        
			// rotate linked node
			param_node.setRotation(param_node.getRotation() * new quat(0, 0, 30.0f * ifps));

			return 1;
		}

		public override int render()
		{
			// The engine calls this function before rendering each render frame: correct behavior after the state of the node has been updated.

			return 1;
		}

		public override int flush()
		{
			// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
			// The engine calls flush() 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

		public override int shutdown()
		{
			// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.

			// saving current material color (check it in the UnigineEditor to see that it was modified)
			material.save();

			return 1;
		}

		public override int destroy()
		{
			// Write here code to be called when the video mode is changed or the application is restarted (i.e. video_restart is called). It is used to reinitialize the graphics context.

			return 1;
		}

		public override int save(Stream 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.

			return 1;
		}

		public override int restore(Stream 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.

			return 1;
		}
	}
}

UnigineScript Implementation#

Below you'll find the UnigineScript implementation of the example described above You can copy and paste it to the *.world file in your project.

myworld.usc

Source code (UnigineScript)
#include <core/unigine.h>
// This file is in UnigineScript language.
// World script, it takes effect only when the world is loaded.
	Node my_node;			// node to which a property with links is assigned
	Property property;		// property with all necessary links
	Material material;		// linked material
	Node param_node;		// linked node
	SoundSource sound;		// sound source to be played 
		
int init() {
	// Write here code to be called on world initialization: initialize resources for your world scene during the world start.

	Player player = new PlayerSpectator();
	player.setPosition(Vec3(0.0f,-3.401f,1.5f));
	player.setDirection(Vec3(0.0f,1.0f,-0.4f));
	engine.game.setPlayer(player);

	// getting the node to which a property with links to assets is assigned 
	my_node = engine.editor.getNodeByName("my_object");

	// getting the property, that will be used to acces all necessary files
	property = my_node.getProperty();

	// getting a material from the corresponding property parameter
	material = property.getParameterMaterial(property.findParameter("some_material"));

	// modifying the material
	material.setParameter("albedo_color", vec4(engine.game.getRandomFloat(0.0f, 1.0f), engine.game.getRandomFloat(0.0f, 1.0f), engine.game.getRandomFloat(0.0f, 1.0f), 1.0f));

	//saving modified material
	material.save();


	// getting the path to the mesh file from the corresponding property parameter
	string mesh_file_name = property.getParameterFile(property.findParameter("some_mesh"));

	//  creating and modifying a mesh by adding a box surface to it
	Mesh mesh = new Mesh(mesh_file_name);
	mesh.addBoxSurface("box", vec3(0.3f, 0.3f,2.0f));

	// saving the modified mesh back to the file
	mesh.save(mesh_file_name);

	// getting the path to the sound file from the corresponding property parameter
	string sound_file_name = property.getParameterFile(property.findParameter("some_file"));

	// getting a linked node from the corresponding property parameter
	param_node = property.getParameterNode(property.findParameter("some_node"));

	// creating and playing a sound from the file
	sound = new SoundSource(sound_file_name);
	sound.setMaxDistance(100.0f);
	sound.setLoop(1);
	sound.play();

	// reporting results to the console
	log.message("Path to mesh file: %s\nPath to sound file: %s\nNode ID: %d\n", mesh_file_name, sound_file_name, param_node.getID());
	return 1;
}

// start of the main loop
int 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.

	float ifps = engine.game.getIFps();

	// changing the material
	material.setParameter("albedo_color", vec4(engine.game.getRandomFloat(0.0f, 1.0f), engine.game.getRandomFloat(0.0f, 1.0f), engine.game.getRandomFloat(0.0f, 1.0f), 1.0f));

	// rotate linked node
	param_node.setRotation(param_node.getRotation() * quat(0, 0, 30.0f * ifps));

	return 1;
}

int render() {
	// The engine calls this function before rendering each render frame: correct behavior after the state of the node has been updated.
	
	return 1;
}

int flush() {
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls flush() 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 shutdown() {
	// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.

	// saving current material color (check it in the UnigineEditor to see that it was modified)
	material.save();
	
	return 1;
}

Component System Workflow#

Notice
This option is currently available for C++ projects only!

If you use the Custom Component System in your project, the following workflow is recommended:

  1. Create a component by inheriting a class from the ComponentBase
  2. Add fields to store links to all necessary nodes and files (materials, meshes, etc.)
  3. Generate a *.prop file for this class.
  4. Open you world in the UnigineEditor and assign the generated component’s property to the desired nodes
  5. Specify all links to nodes, materials, textures, meshes, other files to be used, by dragging them from the Asset Browser directly to the corresponding field of the property in the Parameters window.

    Linking nodes and assets to the property.
  6. An instance of the component is created when you launch your application. This instance has variables providing access to all used assets.
    • to get a node you can simply use the corresponding variable:
      Source code (C++)
      component->node
    • to get a path to file, you can simply use the corresponding variable:
      Source code (C++)
      component->file_param_name
      Now, as you have a path to your file you can use it, for example:
      Source code (C++)
      // to create a node reference
      NodeReference::create(path_to_node_file)
      
      // to load a sound source
      SoundSource::create(path_to_sound_file);

For more detailed information on using the Custom Component system, please see the Custom Component System Usage Example.

Last update: 2018-12-27