Adding Scripts to the Project
Starting coding your project is simple with the use of UnigineScript language (no compilation is required).
For this tutorial we are going to get a primitive object (a box) from a script and add game logic to rotate it.
Step 1. Add The Primitive Object To The World#
- Run the project with the loaded UnigineEditor via the SDK Browser.
- In the Menu Bar, choose Create -> Primitive -> Box to create a box object.
- In Create Box window that opens, specify the size of the box and click OK.
- Place the box somewhere in the world.
- By default, the added node is named Cuboid. Right-click it in the World Hierarchy window and rename it box.
- In the Node tab of the Parameters window, change the position of the box to 0.0, 0.0, 1.0.
- In the Menu Bar, click File -> Save World or press CTRL + S to save the world.
Step 2. Add Script Logic#
There are two methods to add a script to the object:
Method 1: By Editing the .usc World Script File#
To add logic that will rotate the box, you should modify the <your_project_name>.usc world script file in a plain text editor.
- Open the project folder via the SDK Browser.
If there is no Open Folder button, choose Other Actions -> Open Folder.
-
Open <your_project_name>.usc script file located in the data directory by using plain text editor.
#include <core/unigine.h> // This file is in UnigineScript language. // World script, it takes effect only when the world is loaded. 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); 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. return 1; } int 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 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 shutdown() { // Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks. return 1; }
The world script contains the following functions by default:
- init() function is used to create objects and initialize all other necessary resources on the world load.
- update() function is used to code project logic and executed every frame.
- postUpdate() function is used for correction purposes: it implements logic that is executed after updating the node's state.
- updatePhysics() function is used to code physics simulation logic
- shutdown() function is used to code project logic and executed when the world is unloaded.
The following part of the init() function code creates a new free-flying game camera that collides with objects (but does not push or interact with them). Read more about the Engine functions.
// create a new spectator player Player player = new PlayerSpectator(); // place it in the specified point player.setPosition(Vec3(0.0f,-3.401f,1.5f)); // turn it in the specified direction player.setDirection(Vec3(0.0f,1.0f,-0.4f)); // set the player as default one engine.game.setPlayer(player);
Comments were added to explain the meaning of each line of the code. - Add a variable to handle the required box node before the init() function.
We do NOT recommend you to create global variables for the real project.
Node box; // add a box node int init() { /* ... */ }
- Put this code into the init() function to get the box node.
Node box; // add a box node int init() { /* ... */ // get the node by the specified name box = engine.world.getNodeByName("box"); return 1; }
Though nodes can be handled by any of the scripts (world, system or editor one) and UnigineEditor (that loads and stores all the nodes from the .world file), they should be owned only by one of them. Otherwise, such nodes can cause Engine crash or memory leak problems.
See Memory Management article for details. - Set the node transformation in the update() function. Note that it is necessary to scale the rotation angle each frame with the frame duration (because it's different for each individual frame) to get constant angular velocity.
Thus, the resulting script is:
/* ... */ int update() { // check whether the node exists if(box != NULL) { // get the frame duration float ifps = engine.game.getIFps(); // set the angle of rotation float angle = ifps * 90.0f; // get the current transformation of the node and apply rotation mat4 transform = box.getTransform() * rotateZ(angle); // set new transformation to the node box.setTransform(transform); } return 1; }
#include <core/unigine.h> // This file is in UnigineScript language. // World script, it takes effect only when the world is loaded. Node box; // add a box 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); // get the node by the specified name box = engine.world.getNodeByName("box"); 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. // check whether the node exists if(box != NULL) { // get the frame duration float ifps = engine.game.getIFps(); // set the angle of rotation float angle = ifps * 90.0f; // get the current transformation of the node and apply rotation mat4 transform = box.getTransform() * rotateZ(angle); // set new transformation to the node box.setTransform(transform); } return 1; } int 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 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 shutdown() { // Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks. return 1; }
- Save all the changes in the <your_project_name>.usc world script file.
- In the Menu Bar of UnigineEditor, choose File -> Reload World or run the world_reload console command.
- Check the result.
Method 2: By Using WorldExpression Objects#
You can add the script to the box by using the WorldExpression object.
- In the Menu Bar, choose Create -> Logic -> Expression to create the WorldExpression object.
- Place the World Expression box somewhere in the world. The added World Expression node will appear in the World Hierarchy.
- Choose the added WorldExpression node in the World Hierarchy window and go to the Node tab of the Parameters window. Here change the position to 1.0, -1.0, 0.5. Now you have the primitive box and the World Expression object on a plane.
- Specify the Update Distance Limit for the WorldExpression node - it is the maximum distance from the camera up to which the expression shall be executed. Let's set it to 10 units.
- Choose the WorldExpression node in the World Hierarchy and go to the World Expression section of the Parameters window.
- Put the following code into the Source field.
{ // get the WorldExpression node via its internal function Node worldExpression = getNode(); // get the frame duration float ifps = engine.game.getIFps(); // set the angle of rotation float angle = ifps * 90.0f; // get the current transformation of the node and apply rotation mat4 transform = worldExpression.getTransform() * rotateZ(angle); // set new transformation to the node worldExpression.setTransform(transform); }
Curly braces are mandatory!Other ways of attaching scripts to the World Expression object you can read here.
- Make the box node a child of the WorldExpression node by dragging it in the World Hierarchy.
Now the box node is the child of the WorldExpression node. It means that the box object inherits all expression transformations of the World Expression object.
Our expression shall be executed (i.e. the transformation shall be updated according to the script) as long as the distance from the camera to the WorldExpression node does not exceed the specified Update Distance Limit)All the child nodes of the WorldExpression node inherit expression transformations.
- Check the result.
If you reset the position of the box node to its parent position, you will get the same result as in the Method 1.