Programming
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
C++
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
Rendering-Related Classes

Run-time

Game Launch

Warning
Game Framework is deprecated and no longer supported. We can't guarantee the stable work of the framework.

To launch the game you need to use a little improved system script. That is:

  1. Include a header data/framework/game/game_system.h, which implements the framework system script
  2. Call the Game::init() method in theinit() function
  3. Call the Game::update() method in theupdate() function
  4. Call the Game::shutdown() method in theshutdown() function

The example of the Game Framework system script (can be found in the data/framework/game/game_system_script.cpp directory):

Source code (UnigineScript)
#include <unigine.h>
#include <core/scripts/system/system.h>
#include <core/scripts/system/stereo.h>
#include <core/scripts/system/wall.h>

#include <framework/game/game_system.h>

int init() {
  systemInit();
  stereoInit();
  wallInit();
  
  Game::init();
  
  return 1;
}

int shutdown() {
  
  systemShutdown();
  stereoShutdown();
  wallShutdown();
  
  Game::shutdown();
  
  return 1;
}

int update() {
  
  systemUpdate();
  stereoUpdate();
  
  Game::update();
  
  return 1;
}

int render() {
  
  stereoRender();
  wallRender();
  
  return 1;
}

To launch the game, specify the [-game file] parameter as a CLI on the engine loading, where file is a name and a path of the game relatively to the data directory. You can also specify the additional parameter, [-game_level index], where index is number of the level to be loaded. If this parameter is not specified, the first level will be loaded. If there is no levels in the game, the game would not be loaded and the corresponding messages will appear in logs.

The other way to add game path and game level parameters is to specify them right in the engine configuration file in the following way:

Source code (XML)
<!-- a game path parameter -->
<item name="game" type="string">framework/samples/strategy/strategy.game</item>
<!-- a start level parameter -->
<item name="game_level" type="int">0</item>
<!-- a system script path -->
<item name="system_script" type="string">framework/game/game_system_script.cpp</item>
Notice
If new game and level parameters are specified as CLI on the engine loading, their values in the configuration file will be overwritten.

The system script execution sequence:

While framework system script instance initialization, the MasterGame class instance is being created (can be found in thedata/framework/game/master_game.h folder). It controls the framework and the game itself, so it has an information about all the levels, which level is currently running. The level can be loaded or reloaded only through the MasterGame class usage. It is run from the engine initialization till its shutdown.

Level Loading and World Script

Any .world file has the corresponding .cpp file, where the world script is implemented. Game Framework uses the same .cpp files for each world, containing the only line:

Source code (UnigineScript)
#include <framework/game/game_world_script.h>

This is enough for the framework to run.

Warning
Game Framework cannot be loaded if the Unigine Editor is already running. Do not load the framework when the game is loaded, it may cause the engine crash.

The world script execution sequence:

Initialization

An init() function is used to create a game and initialize all of the necessary resources on the game launch.

Initialization starts with all of the game data loading from the XML files to the special class, GameData. After, the standardPlayer class is created in case of the initialization error. Then, theGame class with the specified current loaded world is created.

The Game class loads the level logic (if there is any) and the logic of all of the user entities, and put it in the expression with the"EntitiesLogic" namespace. If the user code has the errors and cannot be compiled, the corresponding message will appear in the logs and the game would not be launched.

After the user logic is compiled, framework creates the instance of the user class Level, applicable for the current loaded level. If the class could not be created, the corresponding message will appear in the logs and the game would not be launched.

The next step is the user code analyzing and checking on the all of the entities properties. All of the user entities are being checked on the presence of the fields. If the fields are found, then the corresponding properties are being checked. If there is any mismatch, the corresponding message will appear in the logs and the game would not be launched.

In the end of the Game class initialization, theLevel class is initialized.

While the Level class initialization all of the integrated framework systems (a scheduler, the event system, the entity pool) are created.

Then framework analyzes all of the level node references and checks if they are entities. If a node reference is an entity, framework creates the user class Entity and assign it a name of the node reference. All of the entities with the assigned names re placed in a special map file, where the entity can be found by a name. If there are two or more entities with the same names, the last entity found during the search will be placed into the map file.

Notice
The entity name is assigned while the creation and cannot be changed dynamically.

At the entity creation, the node of this entity gets the Node::setVariable() function, where variable is an entity instance. Then, while analyzing this node we can assume if this node is an entity or not.

After all of the world entities are created, framework sets their fields. In other words, the set() method is called to the each field. The set() method passes the corresponding parameter from the property. Besides, all of the entities are allocated by the update groups: updateable, flushable or renderable entities. Only after that the user entity and Level class onInit() method is called.

If the node or entities has been added to the world during the initialization, the engine.world.updateSpatial() function is called.

Update, Flush, Render

The update() function is the most important in the framework, as the game logic and process are executed in it. The top feature of the framework is providing of the optimal entities update, dynamic and safe update groups exchange, changing of the order or disabling the entities which do not require the update at all. Also framework implements the safe creation and deletion of the entities, puts the deleted entities in the memory pool for them to be used later. It also provides a periodic call of the functions and their automatic time spreading.

The framework update() function works in the following way:

  1. The engine calls the world script update() function, which calls the Game classupdate() function.
  2. The Game class calls the currently usedLevel class update() function. The level update is split into several parts:
    • All the integrated framework systems (a periodic function calls scheduler, profilers etc.) are updated.
    • The entities, deleted during the last update, are put in the memory pool or completely deleted.
    • The entities, created during the last update are added into the list.
      Notice
      The entity will appear in the entity list only at the next update.
  3. The onPreUpdate() function is called for the level. It is required for the user code, implemented before entities update (for example, controls update).
  4. The onUpdate() function is called for all of the updateable entities.
  5. The onPostUpdate() function is called for the level.

The render() function is called every time after the update(). Framework calls the render() function for the current Level class, which, in turn, calls theonRender() function for the all renderable entities. Then, theonRender() function is called for the current level.

The call of the flush() function is similar to the render(), but takes place in the separate engine thread with the fixed FPS.

onUpdate(), onRender() and onFlush() Functions Calls for Entities

Each entity has onUpdate(), onRender() andonFlush() virtual functions. These functions calls can be disabled if required for the entity. They can be disabled dynamically by calling thesetUpdateable(), setRendereable(int mode) and setFlushable(int mode) functions or by changing Field values set before.

Besides, each entity has the Update Order parameter, which defines the order for entities to be updated. For example, if you need the camera entity to be updated after the character entity, camera entity Update order should be bigger than the character's one.

The Update order parameter can be set in the Game Framework editor or dynamically by calling the setUpdateOrder(int order) entity function. It can take up 17 values from0 to 16 inclusive. The default value is 0. It affects the call orders of the onUpdate(), onRender() and onFlush() functions.

Callbacks

Callbacks or in other words pieces of user code executable by the engine at some convenient time are very important while developing your project. There can be Unigine Widgets callbacks (pressed buttons, unfocused windows), body callbacks (called after collisions), or callbacks caused by Physical or World triggers.

While using callbacks you should remember that all of the user's logic is dynamically compiled into the separate expression with its defined namespace (it's name can be returned by calling a Game::getLogicNamespaceName() method). In other words, while setting callback to the engine's object make sure that you have specified namespaces as prefixes to your function.

The example of Unigine Widgets usage in the custom Entity class:

Source code (UnigineScript)
class MyEntity : Entity {
    
  private:
    WidgetButton button;
    
    // button clicked callback
    void button_clicked() {
      log.message(“my button clicked\n”);
    }
    
    // button callback redirector
    void button_callback_redirector( MyEntity entity) {
      entity.button_clicked();
    }
  public:
    
    void onInit() {
      Gui gui = engine.getGui();
      button = new WidgetButton(gui,"Close");
      gui.addChild(button,GUI_ALIGN_CENTER);
      button.setCallback(GUI_CLICKED,game.getLogicNamespaceName() + "::MyEntity::button_callback_redirector",this);
    }
    
    void onShutdown() {
      // don't forget remove callbacs from widget
      button.setCallback(GUI_CLICKED,NULL);
      // or delete widget
      delete button;
    }
};

At shutdown() you need to unbind all the callbacks from Unigine objects that reference to expressions (at shutdown() the expression along with all user logic is deleted, and if there is a callback binded to the Unigine object, it will reference to the non-existent expression, which may cause an engine crash while calling this callback).

Scheduler

A scheduler provides the periodic call of a function or group of functions and their automatic time spreading. It can manage the complicated logic, for example, if some operations do not need to be counted every update: the path finding, complicated calculations of the object state or even your C++ plugin call with the database request. The time spreading helps to avoid spikes (the sharp increase of the update time if there are a big amount of objects).

The call frequency can vary from 1 to 60 calls per second. The scheduler can call either static or class instance functions.

For the function to be called periodically, in a current loaded level call the Level::setPeriodicUpdate(int instance,string function,int frequency,int priority) function.

Besides, you can pass up to 4 additional arguments when calling the function periodically.

You can safely cancel the function periodic call in any time: in a current loaded level call the Level::removePeriodicUpdate(int instance,string function,int num_args = 0) function.

If you want to change the call frequency for your function, call the Level::setPeriodicUpdate(int instance,string function,int frequency,int priority) for this function with the set new frequency.

You cannot call the same function with different frequencies.

All of the Scheduler functions are divided into groups by the call frequency. When you subscribe to the periodic function update, the function (task), according to its frequency, is automatically moved to the corresponding group. Then the scheduler defines the time for the tick, during which one task is needed to be done. It depends on the number of tasks in the group. For each world update the Scheduler forms the group of tasks that are needed to be performed. If the world update time exceeds the tick time for a group, there will be several tasks from this group in the list. Then the Scheduler sorts the tasks by priority and perform their function calls. Such system provides correct time spreading for complicated tasks and tends to perform tasks with the frequency specified. If the world update time is too big (low FPS), the Scheduler will always perform tasks, but the call frequency for the task will not be guaranteed. If FPS is extremely low, the Scheduler can perform the task more than for one time in one update (in other words, the Scheduler will perform the specified tasks even if there was a hang).

The example of calling the "myPeriodicFunction" function of the MyEntity class to be updated 10 times per second, with the 0 priority and one custom int argument:

Source code (UnigineScript)
class MyEntity : Entity {
  public:
    void onInit() {
      int my_value = 333;
      // set periodic update to ”myPeriodicFunction” function with
      // the frequency 10 times per second, 0 priority and
      // one custom int argument.
      level.setPeriodicUpdate(this,”myPeriodicFunction”,10,0,my_value);
    }
    
    void onShutdown() {
      // remove periodic update
      level.removePeriodicUpdate(this,”myPeriodicFunction”,0);
    }
    
    void onUpdate() {

    }
    
    void onRender() {

    }
    
    void onFlush() {

    }
    
    void myPeriodicFunction(int some_value) {
      log.message(“some_value = %i\n”,some_value);
    }

};

If you want the static function, described and compiled in the user code, to be periodically called, do not forget to specify the framework expression namespace in the prefix (you can get its name by calling the Game::getLogicNamespaceName() function in the current game). For example:

Source code (UnigineScript)
namespace MyNamespace {
  void myPeriodicFunction() {
    log.message(__FUNC__ + “ called\n”);
  }
}

class MyEntity : Entity {
  private:
  public:
    void onInit() {
      level.setPeriodicUpdate(NULL,game.getLogicNamespaceName() +                 
      ”::MyNamespace::myPeriodicFunction”,10,0);
    }
    
    void onShutdown() {
      level.removePeriodicUpdate(NULL,game.getLogicNamespaceName() +                
      ”::MyNamespace::myPeriodicFunction”);
    }
    
    void onUpdate() {

    }
    
    void onRender() {

    }
    
    void onFlush() {

    }

};

Event System

The event system is one of the basic parts of every game. It is needed for interaction of game objects or the world with the other objects. Each user class instance can be attached to the event, so when the event occur, the set functions will be called. Framework allows user to create, call, disable or delete events at any time. Besides, specially for entities there is an event call in the specified area.

To create the event, in the current level call the Level::createEvent(string name) function, so the system will create an event with this name and you will be able to attach to it.

Similarly to the periodic calls scheduler, you can subscribe to the event in the current level by calling the Level::subscribe(string name,int instance,string function) function. You can pass up to 4 user arguments to the function.

You can unsubscribe the function by calling the Level::unsubscribe(string name,int instance,string function,int num_args = 0) function.

You can call the event for the current level by calling the Level::callEvent(string name) function, so the function will be called to all of the subscribers in the level.

There is also a special event call within the specified area. If the subscriber is an Entity class instance, it goes to the special list, for which the call within the specified area is applied. If you call the event by the Level::callEvent(string name,variable p0,variable p1) function, there will be a search depending on thep0 and p1 types through all the nodes (by engine.world.getIntersectionNodes(variable p0, variable p1,int type,int ret_id[]) function). If the node is an entity, the event will be called for it.

p0 and p1 parameters types:

  • vec3 and vec3 — search is performed within the bounding box. Variables set the points of the bounding box minimum and maximum (by x, y, and z axes).
  • vec3 and float — search is performed within the bounding sphere. Variables set the sphere center and its radius.
  • mat4 and mat4 — search is performed within the view frustum. Variables set projection and modelview matrices.
Notice
This function uses world space coordinates.

To enable or disable the event, call the Level::setEventEnabled(string name,int mode) function. You can check if the function is enabled by calling the Level::isEventEnabled(string name) function. You can check if the even exists by calling the Level::isEvent(string name) function.

Notice
Events belong to the world script, so you need to create them every time the level is loaded.

Dynamical Entity Creation And Deletion

By means of framework you can dynamically create or delete entities.

To delete the entity, in the current level call the Level::removeEntity(Entity entity,int mode = 0) function. There are 3 ways modes of the entity deletion:

  • REMOVE_ENTITY_MODE_DEFAULT - delete the instance of the user entity
  • REMOVE_ENTITY_MODE_DELETE_NODE - delete the entity and the node reference connected with it
  • REMOVE_ENTITY_MODE_POOL - put the entity and its node reference into the memory pool. This way is much easier when creating a new entity, as it is already in the pool.

Regardless of the entity deletion mode, the onShutdown() method will be called for it. You should clear your resources, unsubscribe from events and cancel the periodic function call in it.

In order to dynamically create a new entity, in the current level call the Level::createEntity(string type,string name = “”) function. The first step on the entity creation is to check the pool on the such entity type and if it exists, take it from there. If it does not exist, the new entity instance and node reference will be created and correspondence between them will be set. After that, all of the fields will be set to the entity. Lastly, theonInit() function will be called for a new entity.

Notice
The new entity gets into the entity list only on the next update.

There is another feature of the framework: the entity can safely delete itself in any time if the Level::removeEntity(Entity entity,int mode = 0) function is called and the entity itself (entity = this) is passed as an argument.

Entities Interaction

Besides the event system, there are other ways of entities interaction. As user logic is written in the one expression, you can call the methods of one entity to another. For it you need to find the target entity:

When the entity is found, its methods could be called for other entities.

Passing Parameters Through Levels

As the game logic is located in the world script and is reloaded every time the level is reloaded, common parameter for different levels cannot be hold in the world script.

There is an opportunity in Game Framework to hold any user data in the system script and use it on different levels. For example, you can use the results of the game progress or character state in the next level.

There are two functions of the current class instance to set or get the parameter: Game::setGameParameter(string name,variable value) andGame::getGameParameter(string name).

Furthermore, you can save the parameters values into the file after the end of the game. For it, in the system script after the game initialization (the Game::init(); line), add the required parameters by Game::setGameParameter(string name) function, and after the game is ended (before theGame::shutdown()) get the parameters into a file by calling the Game::getGameParameter(string name) function.

Last update: 2017-07-03