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
CIGI Client Plugin
Rendering-Related Classes

File System

The Unigine engine has its own file system module used to access files and folders. It has a few peculiarities you should be aware of when loading resources or organizing the structure of your Unigine-based project.

Data Directory

When the engine is started, it checks for a folder where all resources are stored. This is a data folder specified via the -data_path start-up option. By default, it is the data folder created automatically on project creation via Unigine SDK Browser.

  • Here, all base Unigine resources (shaders, default textures, materials, etc.) are found. Without them, the application cannot be launched.
  • By default, all project resources also need to be stored under this data folder (unless accessed from an external location, as described below).
Notice
The path to the data directory can be specified relatively to the binary executable or as an absolute path.
Absolute paths are always used as is. Relative paths, however, will get /data auto-appended to them if there is a /data subdirectory.
Notice
When the path to the data directory is relative, the engine switches the current directory to the directory with the binary executable.
For example, if the project folder has the following structure:
  • unigine_project
    • bin
    • data
The data path will be unigine_project/data after the application start-up:
Shell commands
bin\main_x86d.exe -data_path "../"

If the -data_path isn't specified (neither in the command-line nor the configuration file), the default ../ path will be used. If multiple -data_path are specified, only the first one will be treat as path to the data folder. Moreover:

  • The engine will check only the first -data_path when searching for the configuration file.
  • The relative path specified in the -gui_path will be taken relatively to the first -data_path.
  • If the -project_name isn't specified, the *.cache files will be saved to the first specified -data_path.
  • When creating a file via the engine file system, if will be created in the folder specified in the first -data_path.
  • When referring to the file created after initialization of the engine file system, the file system will check only the first specified -data_path directory.
Notice
A path specified in the -data_path option won't be written into the configuration file.

Current Directory

When the first specified -data_path is absolute, the current working directory may differ from the directory with the binary executable. However, when the path to the data directory is relative, the engine switches the current directory to the one with the binary executable.

When accessing a file outside the data directory (which is also not specified in an additional -data_path) via API, the path to such file should be specified relative to the current directory. For example:

Source code (C++)
// cbox.mesh is stored outside the data directory, so the path is specified relative to the current directory
ObjectMeshStatic cbox = new ObjectMeshStatic("../../data/cbox.mesh");

External Directories

To extend the file system outside the main data directory, 3 ways are possible:

For the last variant, use the additional -data_path command-line options as follows:

Shell commands
-data_path "../" -data_path "D:/resources/my_project/test_0/" -data_path "../../resources/my_project/test_1/"

Paths can be both relative to the binary executable or absolute.

Notice
Remember that only the first -data_path option specified on the application start-up will be treated as the path to the data folder.

The list of all data paths specified on the application start-up can be obtained via API by using the following methods:

The getDataPath() function without arguments will return the first specified-data_path directory.

ULINK

*.ulink files can be used to add a folder (together with all its sub-folders and UNG or ZIP archives, if any) from outside the data directory to the cached file system. In such files, a path to the directory is stored. A link file can be stored anywhere inside data. On the start-up or when filesystem_reload is called to update the list of resources, the engine scans through files, finds a link file and adds a linked directory as if its root is a data folder.

Paths inside *.ulink files can be relative or absolute.

  • Relative to the data directory:
    Source code
    ../../my_project/resources/
  • An absolute path can be specified.
    For example, on Windows:
    Source code
    D:\resources\
    On Linux:
    Source code
    /username/resources/

Paths

The engine accepts both the relative and absolute paths. Moreover, the files stored in the data folder can be accessed by using partial path.

Partial

Partial paths simplify handling of files. If you want to load a file and you are sure that there is only one resource with a given name in the data folder, only a file name can be provided without a path. If a name is not unique, the first found file with such name is loaded. It is also possible to provide a sub-path that uniquely specifies a file. For example, to load data/project/image_1.tga, you can use image_1.tga (if a name is unique) or project/image_1.tga.

Relative vs Absolute

When relative paths are used, you can relocate your Unigine-based application or copy it onto another machine, and all resources will be properly loaded. There is no loading speed penalty as well: it is as fast as loading files by an absolute path due to name caching.

Notice
It is possible to use absolute paths to load resources outside data folder, but such project will not be be portable.

As file names are cached, usually the same name and path should be used to load and remove file when accessing from your source code by using FileSystem functions:

  • For default resources, functions return full paths relative to the data folder.
  • If you load a file and specify a relative path, use a relative path to delete the resource.
  • If you load a file using an absolute path, use an absolute path to delete the resource.

Case Sensitivity

By default, all of the file names inside archives are case sensitive. It is true even for Windows, where unpacked files are case independent. So, if you have errors on file opening, check the file name case first.

You can make the file system case insensitive by using the filesystem_icase console command.

File System Update

Dynamic Scanning vs Pre-Cached

Dynamic scanning allows the engine to cache names of all files within the data folder on the startup; this enables tracking file changes in real-time.

If the dynamic scanning is not necessary or takes too many resources, you can use the pre-cashed file hierarchy that is specified in the .ulist file. When the engine finds this file in a folder during the startup scanning, it stops and uses the list of files specified there.

The .ulist file is generated by using the special script externs/bin/datalist.py that should be run from the data folder.

Notice
You need to re-generate .ulist files in case you modify contents of folders.
If changes to the file system are made not by means of UnigineScript, you may need to call the filesystem_reload console command.

Automatic Resource Reloading

The dynamic scanning of the file system allows automatic resource reloading. For example, if an artist changes a texture or a mesh file on the disk, it will automatically change in the world.

Notice
In case absolute file paths are used, it is not possible to track changes.
If the dynamic scanning is off, file changes are not tracked in real-time, so the world is not updated, but the engine initialization time is drastically reduced.

File Packages

Types

Unigine supports two types of file archives to save space or pack the production version of resources:

  • UNG (a Unigine-native format for archives created with Archiver tool)
    Notice
    The maximum size for a file inside a UNG archive is limited to 2 GB.
  • ZIP
Besides saving space, archive also speed up resource loading, as files in an archive are read linearly.

UNG and ZIP archives are loaded automatically if they are found within the data folder. File names are cached just like in case with non-archived files.

Content Access

Archives are completely transparent to the engine. There is no need to explicitly unpack the archives, as their content is automatically handled as not packed. Archived files are addressed as if they are non-archived. For example, if you have data/project/archive.ung and want to address directory/file.txt within it, simply specify the following path: project/directory/file.txt or even file.txt (only if the file name is unique).

Notice
By default, all file names inside archives are case sensitive! Even for Windows, where unpacked files are case independent. If you have errors on archived file opening, check the file name case first.

Inside the archive, files can be organized in any way. However, in the root of the archive only files with unique names should be placed. Otherwise, the file search will return incorrect results.

Here is an example of an incorrect file tree for an archive:

  • my_archive.ung
    • my_folder
      • file_2.txt
    • file_1.txt
    • file_2.txt
In this case, there is no problem with file_1.txt, since its name is unique. file_2.txt, on the other hand, will cause problems. Even if you specify a directory in the path name (the full one: my_folder/file_2.txt or a partial one: older/file_2.txt), it does not guarantee that a non-root file will be returned.

The correct archive structure can be specified as follows:

  • my_archive.ung
    • my_folder
      • file_2.txt
    • another_folder
      • file_2.txt
    • file_1.txt
In this case, you can specify directories in the file search and the result will be perfectly correct. For example, full ones: my_folder/file_2.txt, another_folder/file_2.txt or partial ones: another_folder/file_2.txt.

If there is a name collision between an archived file and a non-archived one, the first matching file is returned. The search is performed in the following order:

  1. Unarchived files
  2. Files in UNG archives
  3. Files in ZIP archives

From Unigine API, archives are handled using FilesSystem functions as well.

External Packages

It is possible to add UNG and ZIP archives to the cached file system even if they are stored outside the data folder. External packages are added only on the start-up. Use one of the following methods for that:

  • Add archives on the start-up via -extern_package CLI option (together with the rest of required ones). Relative and absolute paths can be used.
    Shell commands
    bin\main_x86d.exe -extern_package "../../my_project/archive.ung"
    Several packages:
    Shell commands
    bin\main_x86d.exe -extern_package "../2/core.ung" -extern_package "D:/Unigine/core.ung"
  • Using Unigine API, via loadPackage() function.
Notice
The path can be specified relatively to the binary executable or as an absolute path.

As well as the data paths, the paths to external packages won't be saved into the configuration file. However, they can be added to it manually.

Modifiers

File modifiers serve to automatically choose what resources to load when a Unigine project is run on different platforms or with different localizations. Instead of keeping multiple versions of the same project and copying shared data between them, you can add a custom postfix to file or folder names, and load only required resources on demand.

Modifiers are added to file or folder names as a postfix (only one can be specified). Any custom postfix can be used. For example, it could be:

  • File name modifier: file.small.node ortexture.eng.dds
  • Folder name modifier: textures.lowres
    Notice
    If a folder has a modifier, files inside of it should not have modifiers. Otherwise, their modifiers will be ignored.

Register necessary modifiers in code via engine.filesystem.addModifier(). When the project running, resources with the registered modifiers will be automatically loaded. Files without modifiers have the lowest priority (can be used for default resources).

Usage Example

For example, three localization languages are supported in the project: English (by default), German and French. Depending on the language, different splash textures need to be loaded on the start-up.

To organize your resources, name them using the following file modifiers:

  • data
    • splashes
      • splash.png (this would be a default version of the texture. In our case, a texture with an English title)
      • splash.de.png (a German title)
      • splash.fr.png (a French title)

After that, in the code you need to specify what modifier to use via engine.filesystem.addModifier(). This function is called in the system script (unigine.cpp) since a modifier need to be registered before the world and its resources start to be loaded. For example, to load a German splash screen and the low resolution textures interface:

Source code (UnigineScript)
// unigine.cpp

int init() {
	...
	
	// Register modifier
	engine.filesystem.addModifier("de");
	
	// Set a splash texture
	engine.splash.setWorld("textures/splash.png");   // splash.de.png will be automatically used
	...
	
	return 1;
}

Also you can use -extern_define CLI option to pass the language (for example, if a user chooses a language in the launcher).

Shell commands
bin\main_x64d.exe -extern_define "LANG_DE"

And here is how passed defines can be handled in the code.

Source code (UnigineScript)
// unigine.cpp
string lang = "";

int init() {
	...
	
	// Parse EXTERN_DEFINE
	#ifdef LANG_DE
		lang = "de";
	#elif LANG_FR
		lang = "fr";
	#endif
	
	if(lang != "") {
		engine.filesystem.addModifier(lang);
	}
	
	// Set a splash texture: splash.de.png or splash.fr.png will be used if the language is passed
	engine.splash.setWorld("textures/splash.png");	// otherwise, splash.png
	...

	return 1;
}
Last update: 2017-12-21