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:
- 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.
- 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:
- 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:
<?xml version="1.0" encoding="utf - 8"?> <property version="2.7.0.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>
- 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.
- 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:
int AppWorldLogic::init() { /* ... */ NodePtr node = Editor::get()->getNodeByName("node_name"); /* ... */ return 1; }
- Then we get a property assigned to it:
PropertyPtr property = node->getProperty();
- 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:
property->getParameterNode(property->findParameter("node_param_name"));
- to get a path to file, we can simply use:
As we have a path to our file, we can use it, for example:
const char *path = property->getParameterFile(property->findParameter("file_param_name"));
// 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);
- to get a node we can simply use the corresponding node parameter:
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
#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
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
SoundSourcePtr 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
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
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
SoundSource 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.cpp
#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
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
SoundSource 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
If you use the Custom Component System in your project, the following workflow is recommended:
- Create a component by inheriting a class from the ComponentBase
- Add fields to store links to all necessary nodes and files (materials, meshes, etc.)
- Generate a *.prop file for this class.
- Open you world in the UnigineEditor and assign the generated component’s property to the desired nodes
- 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.
- 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:
component->node
- to get a path to file, you can simply use the corresponding variable:
Now, as you have a path to your file you can use it, for example:
component->file_param_name
// to create a node reference NodeReference::create(path_to_node_file) // to load a sound source SoundSource::create(path_to_sound_file);
- to get a node you can simply use the corresponding variable:
For more detailed information on using the Custom Component system, please see the Custom Component System Usage Example.