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
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
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

Class Export

Unigine API supports class export from C++ into UnigineScript along with their:

  • Constructors
  • Member functions (up to eight arguments are supported)
Notice
Member variables cannot be exported. Since it is not possible to access them directly, you need to create accessor methods for them.

See also

An example can be found in the <UnigineSDK>/source/samples/Api/Scripts/Classes/ directory.

Class Export Example

In order to export a C++ class into UnigineScript you need to do the following in the main() function on the C++ side:

  1. Create an external class based on your C++ class via the MakeExternClass() function.
  2. Add constructors to the external class via the Unigine::ExternClass<Class>::addConstructor() function.
  3. Add methods to the external class via the Unigine::ExternClass<Class>::addFunction() function.

    If the method receives an array as an argument, you should specify the array declaration as the last argument of the addFunction() as follows:

    Source code (C++)
    my_object->addFunction("my_array",&MyExternObject::my_array,"[]");
    If the target method has more than one argument, specify the array declaration on the corresponding position:
    Source code (C++)
    // the my_array() method receives an array as the second argument
    my_object->addFunction("my_array",&MyExternObject::my_array,",[],");

    See also the Default Argument Values chapter of the Function Export article and the article on UnigineScript Containers for more details.

  4. Register the external class via Unigine::Interpreter::addExternClass().
  5. Export functions of the external class via the Interpreter::addExternFunction() function.
Notice
Note that static methods of classes are exported as pure functions.
Source code (C++)
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>

#include "AppSystemLogic.h"
#include "AppWorldLogic.h"
#include "AppEditorLogic.h"


using namespace Unigine;
using namespace Math;

/******************************************************************************\
*
* Extern class
*
\******************************************************************************/

/*
*/
class MyExternObject : public Base {
		
	public:
		
		MyExternObject() : mass(0.0f) {
			Log::warning("MyExternObject::MyExternObject(): called\n");
		}
		MyExternObject(const vec3 &size,float mass) : size(size), mass(mass) {
			Log::warning("MyExternObject::MyExternObject((%g,%g,%g),%g): called\n",size.x,size.y,size.z,mass);
		}
		~MyExternObject() {
			Log::warning("MyExternObject::~MyExternObject(): called\n");
		}
		
		// size
		void setSize(const vec3 &s) {
			Log::warning("MyExternObject::setSize((%g,%g,%g)): called\n",s.x,s.y,s.z);
			size = s;
		}
		const vec3 &getSize() const {
			return size;
		}
		
		// mass
		void setMass(float m) {
			Log::warning("MyExternObject::setMass(%g): called\n",m);
			mass = m;
		}
		float getMass() const {
			return mass;
		}
		
	private:
		
		vec3 size;
		float mass;
};

/*
*/
MyExternObject *MakeMyExternObject(const vec3 &size,float mass) {
	return new MyExternObject(size,mass);
}

void DeleteMyExternObject(MyExternObject *object) {
	delete object;
}

/*
 */
void MyExternObjectSetSize(MyExternObject *object,const vec3 &size) {
	object->setSize(size);
}

const vec3 &MyExternObjectGetSize(MyExternObject *object) {
	return object->getSize();
}

/******************************************************************************\
*
* Main
*
\******************************************************************************/

#ifdef _WIN32
	int wmain(int argc,wchar_t *argv[]) {
#else
	int main(int argc,char *argv[]) {
#endif
	
	// export extern class
	ExternClass<MyExternObject> *my_object = MakeExternClass<MyExternObject>();
	my_object->addConstructor();
	my_object->addConstructor<const vec3&,float>();
	my_object->addFunction("setSize",&MyExternObject::setSize);
	my_object->addFunction("getSize",&MyExternObject::getSize);
	my_object->addFunction("setMass",&MyExternObject::setMass);
	my_object->addFunction("getMass",&MyExternObject::getMass);
	Interpreter::addExternClass("MyExternObject",my_object);
	
	// export extern class functions
	Interpreter::addExternFunction("DeleteMyExternObject",MakeExternFunction(&DeleteMyExternObject));
	Interpreter::addExternFunction("MakeMyExternObject",MakeExternFunction(&MakeMyExternObject));
	Interpreter::addExternFunction("MyExternObjectSetSize",MakeExternFunction(&MyExternObjectSetSize));
	Interpreter::addExternFunction("MyExternObjectGetSize",MakeExternFunction(&MyExternObjectGetSize));
	
	AppSystemLogic system_logic;
	AppWorldLogic world_logic;
	AppEditorLogic editor_logic;
	
	Unigine::EnginePtr engine(UNIGINE_VERSION,argc,argv);

	// Enter main loop.
	engine->main(&system_logic,&world_logic,&editor_logic);
	
	
	return 0;
}

Access from Scripts

After the registration, the exported class can be used in UnigineScript just like any other classes.

Source code (UnigineScript)
// my_world.cpp

/*
*/
void extern_object_info(MyExternObject object) {
	
	// call object methods to get its parameters
	vec3 size = object.getSize();
	float mass = object.getMass();
	
	log.message("size is: (%g,%g,%g), mass is: %g\n",size.x,size.y,size.z,mass);
}

/*
*/
int init() {

	/* ... code ... */
	/////////////////////////////////
	
	log.message("\n");
	
	// create an external object using a default constructor
	MyExternObject extern_object = new MyExternObject();
	extern_object_info(extern_object);
	
	// set parameters of the external object
	extern_object.setSize(vec3(10.0f,20.0f,30.0f));
	extern_object_info(extern_object);
	
	// delete the object
	delete extern_object;
	
	/////////////////////////////////
	
	log.message("\n");
	
	// create an object using another constructor
	extern_object = new MyExternObject(vec3(1.0f,2.0f,3.0f),10.0f);
	extern_object_info(extern_object);
	
	// set object parameters
	MyExternObjectSetSize(extern_object,vec3(10.0f,20.0f,30.0f));
	vec3 size = MyExternObjectGetSize(extern_object);
	log.message("size is: (%g,%g,%g)\n",size.x,size.y,size.z);
	
	// delete the object
	delete extern_object;
	
	/////////////////////////////////
	
	log.message("\n");
	
	// create an object using the external class function
	extern_object = MakeMyExternObject(vec3(4.0f,5.0f,6.0f),10.0f);
	extern_object_info(extern_object);
	
	// set object parameters.
	extern_object.setMass(100.0f);
	extern_object_info(extern_object);
	
	// delete the object using the external class function
	DeleteMyExternObject(extern_object);
	
	/////////////////////////////////
	
	return 1;
}

Output

The following results will be printed to the console:

Output
MyExternObject::MyExternObject(): called
size is: (0,0,0), mass is: 0
MyExternObject::setSize((10,20,30)): called
size is: (10,20,30), mass is: 0
MyExternObject::~MyExternObject(): called

MyExternObject::MyExternObject((1,2,3),10): called
size is: (1,2,3), mass is: 10
MyExternObject::setSize((10,20,30)): called
size is: (10,20,30)
MyExternObject::~MyExternObject(): called

MyExternObject::MyExternObject((4,5,6),10): called
size is: (4,5,6), mass is: 10
MyExternObject::setMass(100): called
size is: (4,5,6), mass is: 100
MyExternObject::~MyExternObject(): called

Exporting a Class with a Protected Constructor

If necessary, you can make a protected constructor of a C++ class available from scripts. To export it, you need to declare Unigine::ExternClassConstructor<Class,List,Type> template as a class friend.

Notice
Up to 9 arguments are supported.
You can find the declaration of the template in the <UnigineSDK>/include/UnigineInterpreter.h header file.
Notice
Protected class members cannot be exported.
Source code (C++)
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>

using namespace Unigine;

/******************************************************************************\
*
* User defined class
*
\******************************************************************************/

/*
*/
class MyClass {
		
	protected:
	
		 // declare the template as the friend of the MyClass
		template <class,typename,typename> friend class Unigine::ExternClassConstructor;
		
		// define the first constructor (without arguments)
		MyClass() {
			Log::warning("MyClass::MyClass() is called\n");
		}
		
		// define the second constructor with one argument
		MyClass(int v) {
			Log::warning("MyClass::MyClass(%d) is called\n",v);
		}
};

/******************************************************************************\
*
* Main
*
\******************************************************************************/

/*
 */
int main(int argc,char **argv) {
	
	// export a class.
	ExternClass<MyClass> *my_object = MakeExternClass<MyClass>();
	// add a default constructor without arguments
	my_object->addConstructor();
	// add a constructor with one argument
	my_object->addConstructor<int>();
	// register the exported class.
	Interpreter::addExternClass("MyExternObject",my_object);
	
	// Initialize the engine.
	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
	
	// Enter the main loop.
	engine->main();
	
	// Shut down the engine.
	Engine::shutdown();
	
	return 0;
}

You can also declare the corresponding template as the class friend for each of the protected constructors as follows:

Source code (C++)
class MyClass {
		
	protected:
	
		// declare the templates as the friends of the MyClass
		// one to add the constructor without arguments
		friend class Unigine::ExternClassConstructor<MyClass,MakeTypeList<>::Type>;
		// and another to add the constructor with one argument
		friend class Unigine::ExternClassConstructor<MyClass,MakeTypeList<int>::Type>;
		
		// define the first constructor (without arguments)
		MyClass() {
			Log::warning("MyClass::MyClass() is called\n");
		}
		
		// define the second constructor with one argument
		MyClass(int v) {
			Log::warning("MyClass::MyClass(%d) is called\n",v);
		}
};

Access from Scripts

After that, you can use the exported class in UnigineScript.

Source code (UnigineScript)
// my_world.cpp

int init() {
		
	// create an instance of the exported class
	MyExternObject object_0 = new MyExternObject();
	MyExternObject object_1 = new MyExternObject(1);
	
	return 1;
}

Output

Output
MyClass::MyClass() is called
MyClass::MyClass(1) is called

Exporting Inherited Classes

You can export C++ classes inherited from other C++ classes into UnigineScript and use them like other classes. Both base and derived classes are exported as it was described above. For each derived class you should add a base class using the Unigine::ExternClass<Class>::addBaseClass() function.

Source code (C++)
my_derived_class->addBaseClass(my_base_class);
In this example we declare and export the following classes:
  • MyBase - a base class
  • MyNode - a class inherited from the MyBase class
  • MyObject - a class inherited from the MyNode class
Source code (C++)
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>

#include "AppSystemLogic.h"
#include "AppWorldLogic.h"
#include "AppEditorLogic.h"


using namespace Unigine;

//////////////////////////////////////////////////////////////////////////
// User defined class
//////////////////////////////////////////////////////////////////////////

class MyBase
{
public:
	MyBase()
	{
		Log::warning("MyBase::MyBase(): called\n");
	}
	virtual ~MyBase()
	{
		Log::warning("MyBase::~MyBase(): called\n");
	}

	void function()
	{
		Log::warning("MyBase::function(): called\n");
	}

	virtual const char *getName() = 0;
};

class MyNode : public MyBase
{
public:
	MyNode()
	{
		Log::warning("MyNode::MyNode(): called\n");
	}
	virtual ~MyNode()
	{
		Log::warning("MyNode::~MyNode(): called\n");
	}

	void function()
	{
		Log::warning("MyNode::function(): called\n");
	}

	virtual const char *getName()
	{
		return "MyNode";
	}
};

class MyObject : public MyNode
{
public:
	MyObject()
	{
		Log::warning("MyObject::MyObject(): called\n");
	}
	virtual ~MyObject()
	{
		Log::warning("MyObject::~MyObject(): called\n");
	}

	void function()
	{
		Log::warning("MyObject::function(): called\n");
	}

	virtual const char *getName()
	{
		return "MyObject";
	}
};

/******************************************************************************\
*
* Main
*
\******************************************************************************/
int main(int argc, char **argv)
{
#ifdef _WIN32
	int wmain(int argc,wchar_t *argv[]) {
#else
	int main(int argc,char *argv[]) {
#endif
	
	// Make a base class
	ExternClass<MyBase> *my_base = MakeExternClass<MyBase>();

	// add functions
	my_base->addFunction("function", &MyBase::function);
	my_base->addFunction("getName", &MyBase::getName);
	
	// register the MyBase class.
	Interpreter::addExternClass("MyBase", my_base);
	
	// Make a class inherited from the MyBase
	ExternClass<MyNode> *my_node = MakeExternClass<MyNode>();

	// add a default constructor without arguments
	my_node->addConstructor();
	my_node->addFunction("function", &MyNode::function);
	
	// add base class for MyNode class
	my_node->addBaseClass(my_base);
	
	// register the MyNode class.
	Interpreter::addExternClass("MyNode", my_node);
	
	// Make a class inherited from the MyNode
	ExternClass<MyObject> *my_object = MakeExternClass<MyObject>();

	// add a default constructor without arguments
	my_object->addConstructor();

	// add a function
	my_object->addFunction("function", &MyObject::function);
	
	// add base class for MyObject class
	my_object->addBaseClass(my_node);
	
	// register the MyObject class.
	Interpreter::addExternClass("MyObject", my_object);
	
	AppSystemLogic system_logic;
	AppWorldLogic world_logic;
	AppEditorLogic editor_logic;
	
	// init engine
	Unigine::EnginePtr engine(UNIGINE_VERSION,argc,argv);

	// Enter main loop.
	engine->main(&system_logic,&world_logic,&editor_logic);
	
	
	return 0;
}

Access from Scripts

After the registration, the exported classes can be used in UnigineScript just like any other classes.

Source code (UnigineScript)
// my_world.cpp

/*
*/

int init() {
	/////////////////////////////////

	log.message("\n");

	// object class
	MyObject object = new MyObject();
	object.function();
	log.message("%s\n", object.getName());

	// node class
	MyNode node = object;
	node.function();
	log.message("%s\n", node.getName());

	// base class
	MyBase base = node;
	base.function();
	log.message("%s\n", base.getName());

	// delete object
	delete object;

	/////////////////////////////////

	// show console
	engine.console.setActivity(1);

}

Output

The following results will be printed to the console:

Output
MyBase::MyBase(): called
MyNode::MyNode(): called
MyObject::MyObject(): called
MyObject::function(): called
MyObject
MyNode::function(): called
MyObject
MyBase::function(): called
MyObject
MyObject::~MyObject(): called
MyNode::~MyNode(): called
MyBase::~MyBase(): called

Memory Management for External Classes

By both creating and deleting variables that refer to the external classes, the corresponding scope should be set (world / system / editor). You should use pointers to the corresponding interpreter that are obtained via the following functions in order to set the required scope:

Also you can use pointer to the current interpreter obtained via the Unigine::Interpreter::get() function. If this function is called by the world interpreter, the current interpreter will be the world interpreter.

Source code (C++)
Interpreter *interpreter = Unigine::Interpreter::get();
Notice
If a C++ function is called from the script (world, system and editor), it means the current scope is already set and there is no need to call functions listed above.

If the corresponding scope is not set, memory leaks can occur when creating or deleting a variable.

For example, if you have a function defined on the script side and want to call it from the C++ code with a variable of the external class as an argument, you should set the script runtime:

Source code (C++)
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>

#include <string>

using namespace Unigine;

class MyExternClass {

    public:	

        MyExternClass() {}
        MyExternClass(const std::string &m) { my_member = m; }
        MyExternClass(const MyExternClass &other) { my_member = other.my_member; }
        ~MyExternClass() {}

    private:

        std::string my_member;

};

void my_update() {

    MyExternClass mec("hello!!!\n");
    Engine *engine = Engine::get();
	// get a pointer to the world interpreter 
	Interpreter *world = (Interpreter*)engine->getWorldInterpreter();
	// create a variable of the external class
	Unigine::Variable v(world,TypeInfo(TypeID<MyExternClass*>()),new MyExternClass(mec),1,1);
	// specify the name of the function to call
	Unigine::Variable name("onMyUpdate");
	// run the world script function with the variable of the MyExternClass as the argument
   	engine->runWorldFunction(name,v);
	
}

int main(int argc,char **argv) {

    ExternClass<MyExternClass> *mec = MakeExternClass<MyExternClass>();
    Interpreter::addExternClass("MyExternClass",mec);
    
    Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
    while(engine->isDone() == 0) {
		engine->update();
		engine->render();
		engine->swap();
       
        my_update();
    }

    Engine::shutdown();
    return 0;
}
Notice
In the example above Variable v is a static variable, that is why when leaving the scope of its visibility, it is necessary to reset the context.
Source code (UnigineScript)
// the world script function which receives a variable referring to the external class
void onMyUpdate(MyExternClass v) {
	// some code
}

int init() {
	// some code
	return 1;
}

int shutdown() {
	// some code
	return 1;
}

int update() {
	// some code
	return 1;
}
Last update: 2018-04-26