UnigineScript
The Language
Core Library
Engine Library
Node-Related Classes
GUI-Related Classes
Plugins Library
High-Level Systems
Samples
C++ API
API Reference
Integration Samples
Usage Examples
C++ Plugins
Content Creation
Materials
Unigine Material Library
Tutorials

Creating C++ Application

A Unigine-based application can be implemented by means of C++ only, without using UnigineScript.

See also

Setting Up the File Structure

  1. Create a folder where you will store your project.
  2. Form the structure of your project files. The recommended structure is the following:
    • bin - should contain all of the required dynamic libraries: *.dll (on Windows), *.so (on Linux) or *.dylib (on Mac OS X).
    • lib - should contain all of the required libraries (*.lib files).
      Notice
      This folder is optional. You can refer to the <UnigineSDK>/lib folder when building your application.
    • include - contains all of the required header files used for application implementation. This folder should be copied from the SDK folder as it is.
      Notice
      This folder is optional. You can refer to the <UnigineSDK>/include folder when building your application.
    • data - contains project assets, the simple world and system scripts, the *.world and configuration (*.cfg) files.
      Notice
      The system script is required if your application should be rendered in one of the stereo or multi-monitor mode. Also the system script enables to open the main menu.
      Notice
      The *.world file is required since it is used to load the world. And the scene cannot be shown on the screen if the world has not been loaded.
      Notice
      If you are not going to write logic in UnigineScript, leave functions of the world script empty.
      Source code (UnigineScript)
      int init() {
      	return 1;
      }
      
      int shutdown() {
      	return 1;
      }
      
      int update() {
      	return 1;
      }
      									
    • src - contains the application source code in C++ and build script (e.g. SConstruct or Makefile).

Implementing Application

Notice
You should prepare your development environment before implementing the application.

There are two ways to implement your Unigine-based application in C++:

  • Create a custom class, inherit it from the Unigine::App class, override all of the virtual functions specified in the include/UnigineApp.h file and implement the required application logic.

    Notice
    In this case, creation of the application window and parsing of the operating system messages should be handled by the developer.
    Notice
    You should implement the logic for each operating system on which the application will run.

    See also samples in the <UnigineSDK>/source/samples/App folder.

  • Implement custom update(), render() and swap() functions and call each of them after (or before) the corresponding stage of the main loop. For example:
    Source code (C++)
    #include <Unigine.h>
    
    /*
     */
    void my_update() {
    	// custom update
    }
    
    /*
     */
    void my_render() {
    	// custom render
    }
    
    /*
     */
    void my_swap() {
    	// custom swap
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// initialize the engine
    	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
    	
    	// enter the main loop 
    	while(engine->isDone() == 0) {
    		
    		// update in the main loop
    		engine->update();
    		// application update 
    		my_update();
    		
    		// render in the main loop
    		engine->render();
    		// application render
    		my_render();
    		
    		// swap in the main loop
    		engine->swap();
    		// application physics swap
    		my_swap();
    	}
    	
    	// shutdown the engine
    	Engine::shutdown();
    	
    	return 0;
    }
    							
    Notice
    In this case, you also should implement the logic for each operating system on which the application will run.

    See also samples in the <UnigineSDK>/source/samples/Api folder.

Application Example

The example below demonstrates how to implement the application by using the second way described above.

To create a simple application, which renders objects placed on an empty plane, perform the following steps:

  1. Include all of the required header files stored in the include folder and implement the main function, which performs the world loading and main loop execution.
    Source code (C++)
    #include <Unigine.h>
    #include <UnigineConsole.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// initialize the engine
    	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
    	
    	// load the world via the console
    	Console *console = Console::get();
    
    	console->run("world_load my_project/my_project");
    	console->flush();
    	
    	// enter the main loop 
    	while(engine->isDone() == 0) {
    		
    		engine->update();
    		engine->render();
    		engine->swap();
    	}
    	
    	// shutdown the engine
    	Engine::shutdown();
    	
    	return 0;
    }
    							
  2. Declare pointers to a player, world light and nodes that will be added into the world.
    Notice
    The array of node pointers, pointers to the player and world light must be declared as global after the using directive.
    Source code (C++)
    PlayerDummyPtr player;
    LightWorldPtr light;
    // array of nodes that will be added to the world 
    std::vector<NodePtr> nodes; 
    							
    Notice
    The nodes array is an instance of the std::vector<NodePtr> template class where the <NodePtr> specifies the type of the vector elements.
  3. Declare the transformation matrix for the nodes that will be added into the world:
    Source code (C++)
    UNIGINE_MAT4 transforms[] = {
    	translate(UNIGINE_VEC3(-2.0f,0.0f,0.0f)),
    	translate(UNIGINE_VEC3(2.0f,0.0f,0.0f)),
    	translate(UNIGINE_VEC3(0.0f,-2.0f,0.0f)),
    	translate(UNIGINE_VEC3(0.0f,2.0f,0.0f)),
    };	
    							
  4. Implement your custom my_init() function, which sets the player and world light and fill this world with nodes.
    Source code (C++)
    void my_init() {
    		
    	// create the player
    	player = PlayerDummy::create();
    
    	// create the world light
    	light = LightWorld::create(vec4(1.0f));
    	// enable light scattering
    	light->setScattering(1);
    
    	// create a plane from the plane.node file
    	nodes.push_back(create_node("my_project/nodes/plane.node",UNIGINE_MAT4()));
    	
    	// add nodes specified in the cbox.node file into the world 
    	for(int i = 0; i < 4; ++i) {
    		nodes.push_back(create_node("my_project/nodes/cbox.node",transforms[i]));
    	}
    }
    							
    Notice
    This function should be called before the engine initialization in the main function.
    Notice
    The cbox.mesh specified in the cbox.node file can be found in the <UnigineSDK>/source/samples/Api/data folder.
    The create_node() function creates a node from the given *.node file, assigns ownership of this node to the engine and adds it to the array of pointers.
    Source code (C++)
    NodePtr create_node(const char *name,const UNIGINE_MAT4 &transform) {
    	
    	// the world interface
    	World *world = World::get();
    	
    	// load the node with a given name
    	NodePtr node = world->loadNode(name,0);
    	
    	// remove the owner flag
    	node->release();
    	
    	// set the transformation matrix for the node
    	node->setWorldTransform(transform);
    
    	return node;
    
    }
    							
  5. To control updating of the player node in the main loop, implement your own update function. For example:
    Source code (C++)
    void my_update() {
    	Game *game = Game::get();
    	float time = game->getTime();
    
    	float x = sinf(time * 1.0f) * 4.0f;
    	float y = cosf(time * 1.0f) * 4.0f;
    	float z = 2.0f + sinf(time * 3.0f) * 1.0f;
    	
    	// rotate the player around boxes in the scene
    	player->setWorldTransform(setTo(UNIGINE_VEC3(x,y,z),UNIGINE_VEC3(0.0f,0.0f,0.0f),vec3(0.0f,0.0f,1.0f)));
    	game->setPlayer(player->getPlayer());
    }
    							
  6. Call the my_update() in the main() function after the main loop update:
    Source code (C++)
    engine->update();
    my_update();
    							
  7. Implement a custom shut down logic to clear the array of node pointers and the pointers to the player and world light:
    Source code (C++)
    void my_shutdown() {
    	
    	// clear nodes
    	player.clear();
    	light.clear();
    
    	for(size_t i = 0; i < nodes.size(); ++i) {
    		remove_node(nodes[i]);
    	}
    	nodes.clear();
    }
    							
    The remove_node() function assigns the ownership of the given node and its children to the NodePtr class.
    Source code (C++)
    void remove_node(NodePtr node) {
    
    	// set the owner flag
    	node->grab();
    	
    	// remove children
    	for(int i = node->getNumChilds() - 1; i >= 0; i--) {
    		remove_node(node->getChild(i));
    	}
    }
    							

After performing all of the steps above, your application code should be as follows:

Source code (C++)
#include <Unigine.h>
#include <UnigineConsole.h>
#include <UnigineGame.h>
#include <UnigineLightWorld.h>
#include <UnigineMaterials.h>
#include <UniginePlayerDummy.h>
#include <UnigineWorld.h>

#include <vector>

/*
 */
using namespace Unigine;

/*
 */
PlayerDummyPtr player;
LightWorldPtr light;

std::vector<NodePtr> nodes;

UNIGINE_MAT4 transforms[] = {
	translate(UNIGINE_VEC3(-2.0f,0.0f,0.0f)),
	translate(UNIGINE_VEC3(2.0f,0.0f,0.0f)),
	translate(UNIGINE_VEC3(0.0f,-2.0f,0.0f)),
	translate(UNIGINE_VEC3(0.0f,2.0f,0.0f)),
};

/*
 */
NodePtr create_node(const char *name,const UNIGINE_MAT4 &transform) {
	
	// the world interface
	World *world = World::get();
	
	// load a node with a given name
	NodePtr node = world->loadNode(name,0);
	
	// remove the owner flag
	node->release();
	
	// set the transformation matrix for the node
	node->setWorldTransform(transform);
	
	return node;
}

/*
 */
void remove_node(NodePtr node) {

	// set the owner flag
	node->grab();
	
	// remove children
	for(int i = node->getNumChilds() - 1; i >= 0; i--) {
		remove_node(node->getChild(i));
	}
}

/*
 */
void my_update() {
	Game *game = Game::get();
	float time = game->getTime();

	float x = sinf(time * 1.0f) * 4.0f;
	float y = cosf(time * 1.0f) * 4.0f;
	float z = 2.0f + sinf(time * 3.0f) * 1.0f;

	player->setWorldTransform(setTo(UNIGINE_VEC3(x,y,z),UNIGINE_VEC3(0.0f,0.0f,0.0f),vec3(0.0f,0.0f,1.0f)));
	game->setPlayer(player->getPlayer());
}

/*
 */
void my_init() {
	// create the player
	player = PlayerDummy::create();

	// create the world light
	light = LightWorld::create(vec4(1.0f));
	light->setScattering(1);

	// create the plane from the file
	nodes.push_back(create_node("my_project/nodes/plane.node",UNIGINE_MAT4()));

	// fill the world with nodes
	for(int i = 0; i < 4; ++i) {
		nodes.push_back(create_node("my_project/nodes/cbox.node",transforms[i]));
	}
}
/*
 */
void my_shutdown() {
	
	// clear nodes
	player.clear();
	light.clear();

	for(size_t i = 0; i < nodes.size(); ++i) {
		remove_node(nodes[i]);
	}
	nodes.clear();
}

/*
 */
int main(int argc,char **argv) {
	
	// initialize the engine
	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);

	// load the world
	Console *console = Console::get();

	console->run("world_load my_project/my_project");
	console->flush();
	
	// initialize the player, world light and other nodes
	my_init();
	
	// enter the main loop 
	while(engine->isDone() == 0) {
		
		// update in the main loop
		engine->update();
		// update the application
		my_update();
		
		// render in the main loop
		engine->render();
		
		// swap in the main loop
		engine->swap();
	}
	
	// shut down the player, world light and other nodes
	my_shutdown();
	
	// shut down the engine
	Engine::shutdown();
	
	return 0;
}
					

Building Application

Before running the application, you should build it in a way that corresponds to the target platform. As the result, you will get a binary executable file.

Windows Application

On Windows, you can compile the application by using one of the following options:

  • Via MS Visual Studio by choosing the Debug -> Build Solution in the main menu
    Notice
    Do not forget to specify paths to the required header files and libraries in the Property Pages panel.
  • Via SCons. You should implement the SConstruct script in this case and run the scons from the command prompt. Read more about SCons.
  • Via Nmake. In this case, you need to implement the Makefile.win32 script.
    Source code
    CXX = cl.exe
    LINK = link.exe
    # specify the name of the output binary executable
    TARGET = main.exe
    OBJS = main.obj
    
    # specify the path to the include folder of the project
    CFLAGS = $(CFLAGS) /MD /EHsc /W3 /Osixy /GR /D_CRT_SECURE_NO_DEPRECATE /I"$(UNIGINE_DIR)/include"
    
    # specify the path to the lib folder
    LDFLAGS = $(LDFLAGS) /libpath:"$(UNIGINE_DIR)/lib"
    
    # check whether the version is double or release
    !IF "$(debug)" != "0"
    POSTFIX = d
    !ELSE
    POSTFIX = 
    !ENDIF
    
    # add the custom compiler DUNIGINE_DOUBLE flag  
    # for double precision support 
    !IF "$(double)" == "1"
    CFLAGS = $(CFLAGS) /DUNIGINE_DOUBLE
    !ENDIF
    
    # choose the library to load
    # for the 64-bit version
    !IF "$(MSVCVER)" == "Win64"
    LIBS = $(LIBS) Unigine_x64$(POSTFIX).lib
    # choose the library to load
    # for the 32-bit version
    !ELSE
    LIBS = $(LIBS) Unigine_x86$(POSTFIX).lib
    !ENDIF
    
    .cpp.obj:
    	$(CXX) /nologo $(CFLAGS) /c /Fo$@ $<
    
    $(TARGET): $(OBJS)
    	$(LINK) /nologo $(LDFLAGS) /out:$(TARGET) $(OBJS) $(LIBS)
    
    clean:
    	del $(TARGET) $(OBJS) log.html *.exp *.lib
    							

    In the code above, the $(UNIGINE_DIR) is the environment variable that contains the path to the Unigine SDK.

    Then, you should type the following in the command prompt to compile the application (type make_x64 instead of make_x86 for a 64-bit build):
    Shell commands
    make_x86 -f Makefile.win32
    							
    Notice
    To build the application with double precision coordinates, specify the double=1 option.

See Also

Linux Application

On Linux, the following options can be used to compile the application:

  • Via SCons. You should implement the SConstruct script in this case and run the scons from the terminal. Read more about SCons.
  • Via GNU Make using Makefile. In this case, you need to implement the Makefile script.
    Source code
    CXX = g++
    # specify the name of the output binary executable
    TARGET = main
    # specify the application source file
    SRCS = main.cpp
    DEPEND = .depend
    
    CFLAGS += -Wall -Os -I$(UNIGINE_DIR)/include
    LDFLAGS += -L$(UNIGINE_DIR)/lib
    
    # check whether the version is double or release
    ifneq ($(debug),0)
    	POSTFIX = d
    else
    	POSTFIX = 
    endif
    
    # add the custom compiler DUNIGINE_DOUBLE flag  
    # for double precision support
    ifeq ($(double),1)
    	CFLAGS += -DUNIGINE_DOUBLE
    endif
    
    # choose the library to load
    # for the 64-bit version
    ifeq ($(shell arch),x86_64)
    	LIBS += -lUnigine_x64$(POSTFIX)
    else
    # for the 32-bit version
    	LIBS += -lUnigine_x86$(POSTFIX)
    endif
    
    TEMP = $(SRCS:.c=.o)
    OBJS = $(TEMP:.cpp=.o)
    
    $(DEPEND):
    	$(CXX) $(CFLAGS) -MM $(SRCS) > $@
    
    .cpp.o:
    	$(CXX) $(CFLAGS) -c -o $@ $<
    
    $(TARGET): $(OBJS)
    	$(CXX) $(LDFLAGS) -o $@ $^ $(LIBS)
    
    clean:
    	rm -f $(TARGET) $(OBJS) $(DEPEND) log.html
    
    -include $(DEPEND)
    							

    In the code above, the $(UNIGINE_DIR) is the environment variable that contains the path to the Unigine SDK.

    Then, type the following in the terminal to compile the application:
    Shell commands
    make
    							
    Notice
    To build the application with double precision coordinates, specify the double=1 option.

See Also

Mac OS X Application

On Mac OS X, you can compile the application in one of the following ways:

  • Via XCode by choosing the Product -> Build in the main menu.
    Notice
    Do not forget to specify paths to the required header files and libraries.
  • Via SCons. You should implement the SConstruct script in this case and run the scons from the command-line terminal. Read more about SCons.
  • Via make using Makefile. In this case, you should implement the Makefile script.
    Notice
    You can slightly modify the Makefile script, which is used to build the Linux application. You should add a condition that checks if the current operating system is Mac OS X while choosing the library to load.
    Source code
    # if the current OS is Mac OS X 
    ifeq ($(shell uname),Darwin)
    	# override flags and libraries
    	SYSROOT = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk
    	CFLAGS += -mmacosx-version-min=10.7 -isysroot $(SYSROOT) -Wno-overloaded-virtual
    	LDFLAGS += -mmacosx-version-min=10.7 -isysroot $(SYSROOT)
    	LIBS += -lUnigine_x64$(POSTFIX)
    else ifeq ($(shell arch),x86_64)
    	LIBS += -lUnigine_x64$(POSTFIX)
    else
    	LIBS += -lUnigine_x86$(POSTFIX)
    endif
    							
    Type the following in the command-line terminal to compile the application:
    Shell commands
    make
    							
    Notice
    To build the application with double precision coordinates, specify the double=1 option.

See Also

Running Application

  • To run the application on Windows, specify the following on the application start-up:
    Shell commands
    main.exe -data_path ../ -engine_config my_project/unigine.cfg -system_script my_project/unigine.cpp 
    						
    The main.exe binary executable is created after building the application.
  • On Linux and Mac OS X, you should specify the following on the application start-up:
    Shell commands
    main -data_path ../ -engine_config my_project/unigine.cfg -system_script my_project/unigine.cpp 				
    						
    The main binary executable is created after building the application for Linux or Mac OS X.

The resulting scene is the following:

Last update: 2017-07-03