Creating C++ Application
A Unigine-based application can be implemented by means of C++ only, without using UnigineScript.
See also
- Articles on Typical Architecture of a Unigine-Based Application and Engine Architecture for better understanding of the Unigine C++ API operation in the engine architecture.
- Examples located in the <UnigineSDK>/source/sample/Api and <UnigineSDK>/source/sample/App folders.
- Articles in the Development for Different Platforms section to learn more on how to prepare the development environment, install the Unigine SDK and build the application for different platforms.
Setting Up the File Structure
- Create a folder where you will store your project.
-
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).
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.
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.
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.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.If you are not going to write logic in UnigineScript, leave functions of the world script empty.
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
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.
In this case, creation of the application window and parsing of the operating system messages should be handled by the developer.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:
#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; }
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:
-
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.
#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; }
-
Declare pointers to a player, world light and nodes that will be added into the world.
The array of node pointers, pointers to the player and world light must be declared as global after the using directive.
PlayerDummyPtr player; LightWorldPtr light; // array of nodes that will be added to the world std::vector<NodePtr> nodes;
The nodes array is an instance of the std::vector<NodePtr> template class where the <NodePtr> specifies the type of the vector elements. -
Declare the transformation matrix for the nodes that will be added into the world:
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)), };
- Implement your custom my_init() function, which sets the player and world light and fill this world with nodes.
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])); } }
This function should be called before the engine initialization in the main function.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.The cbox.mesh specified in the cbox.node file can be found in the <UnigineSDK>/source/samples/Api/data folder.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; }
-
To control updating of the player node in the main loop, implement your own update function. For example:
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()); }
-
Call the my_update() in the main() function after the main loop update:
engine->update(); my_update();
-
Implement a custom shut down logic to clear the array of node pointers and the pointers to the player and world light:
The remove_node() function assigns the ownership of the given node and its children to the NodePtr class.
void my_shutdown() { // clear nodes player.clear(); light.clear(); for(size_t i = 0; i < nodes.size(); ++i) { remove_node(nodes[i]); } nodes.clear(); }
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:
#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
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.
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):make_x86 -f Makefile.win32
To build the application with double precision coordinates, specify the double=1 option.
See Also
- Article on Windows Application Development to learn more about building the application.
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.
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:make
To build the application with double precision coordinates, specify the double=1 option.
See Also
- Article on Linux Application Development to learn more about building the application.
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.
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.
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.Type the following in the command-line terminal to compile the application:
# 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
make
To build the application with double precision coordinates, specify the double=1 option.
See Also
- Article on Mac OS X Application Development to learn more about building the application.
Running Application
-
To run the application on Windows, specify the following on the application start-up:
The main.exe binary executable is created after building the application.
main.exe -data_path ../ -engine_config my_project/unigine.cfg -system_script my_project/unigine.cpp
-
On Linux and Mac OS X, you should specify the following on the application start-up:
The main binary executable is created after building the application for Linux or Mac OS X.
main -data_path ../ -engine_config my_project/unigine.cfg -system_script my_project/unigine.cpp
The resulting scene is the following: