This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
专业(SIM)
UnigineEditor
界面概述
资源工作流程
版本控制
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
如何擴展編輯器功能
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
VR Development
双精度坐标
应用程序接口
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
创建内容
内容优化
材质
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

将模型直接导入内存

警告
本文介绍的功能在 Community SDK 版本中不可用。
您应该升级到 Engineering / Sim SDK版本才能使用它。

UNIGINE的 Import System允许您以各种外部格式(FBX, DAE, OBJ等)导入模型和场景。默认情况下(即当使用DefaultProcessor时),对于每个导入的场景或模型,都会在磁盘上创建一组UNIGINE文件格式的对应文件(.mesh, .dds, .node,等)。

然而,在某些情况下,可能需要直接将模型导入到当前场景中,而不需要在磁盘上创建任何不必要的文件(例如,为智能城市应用程序加载建筑物模型等)。此外,由于减少了缓慢的磁盘I/O操作,这种方法可以加快整个导入过程。

为此,我们应该创建一个自定义的导入处理器

这个例子演示了如何:

  • FbxImporter插件创建自己的自定义导入处理器。
  • 使用您的自定义导入处理器将存储在FBX文件中的场景带到当前加载的UNIGINE场景。
注意
建议阅读 Import System文章,并熟悉基本导入功能类

在我们开始之前,先讲一点理论(作为重点):

  • UNIGINE API为我们提供了一组的类,用于实现自定义模型导入。
  • 关于导入场景的所有元信息都应该存储在ImportScene类的一个实例中。基本上,场景可能包含网格(ImportMesh)、灯光(ImportLight)、摄像机(ImportCamera)和其他元素(参见完整列表 here).
  • 自定义导入处理器应该从ImportProcessor类继承。

Creating a Custom Import Processor创建自定义导入处理器#

让我们从处理器开始。我们称它为MemoryProcessor,并实现在当前UNIGINE世界中从fbx文件的内容创建对象,跳过不必要的数据保存到磁盘。因此,我们将简单地运行导入的FBX场景的组件(摄像机、灯光和网格),构建相应的节点层次结构,添加到世界中,并创建所有必要的材质和纹理。下面的代码补充了解释该过程基本方面的注释。

Direct Memory Import Processor

源代码 (C++)
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineMeshStatic.h>

using namespace Unigine;
// our custom ImportProcessor to be used for direct memory import of scenes
class MemoryProcessor : public ImportProcessor
{
public:

	void convertNode(const NodePtr &node_parent, const ImportNodePtr &import_node_parent,
		NodePtr &node, const ImportNodePtr &import_node)
	{
		using namespace Unigine::Math;
		if (ImportCameraPtr import_camera = import_node->getCamera())
		{
			node = getImporter()->importCamera(getImportProcessor(), import_camera);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportLightPtr import_light = import_node->getLight())
		{
			node = getImporter()->importLight(getImportProcessor(), import_light);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportMeshPtr import_mesh = import_node->getMesh())
		{
			ObjectPtr object;
			if (import_mesh->isHasAnimations())
			{
				float fps = getImporter()->getParameterFloat("fps");
				auto mesh_skinned = ObjectMeshSkinned::create(meshes[import_mesh->getFilepath()]);
				if (MeshPtr animation = meshes_animations.value(import_mesh->getFilepath()))
				{
					int animation_id = mesh_skinned->addAnimation(animation);
					mesh_skinned->setNumLayers(1);
					mesh_skinned->setAnimation(0, animation_id);
				}
				mesh_skinned->setSpeed(fps);
				object = mesh_skinned;
			} else
			{
				auto mesh_static = ObjectMeshStatic::create();
				mesh_static->setMeshProceduralMode(true);
				mesh_static->applyMeshProcedural(meshes[import_mesh->getFilepath()]);

				object = mesh_static;
				object->setWorldTransform(Mat4(import_node->getTransform()));
			}

			int num_geometries = import_mesh->getNumGeometries();
			for (int i = 0; i < num_geometries; ++i)
			{
				ImportGeometryPtr geometry = import_mesh->getGeometry(i);
				int num_surfaces = geometry->getNumSurfaces();
				for (int s = 0; s < num_surfaces; ++s)
				{
					ImportSurfacePtr surface = geometry->getSurface(s);
					const int surface_index = surface->getTargetSurface();
					if (surface_index == -1 || surface_index >= object->getNumSurfaces())
					{
						Log::error("MemoryProcessor: can't find surface \"%s\".\n", surface->getName());
						continue;
					}

					if (!compare(surface->getMinVisibleDistance(), -Consts::INF))
					{
						object->setMinVisibleDistance(surface->getMinVisibleDistance(),
							surface_index);
					}
					if (!compare(surface->getMaxVisibleDistance(), Consts::INF))
					{
						object->setMaxVisibleDistance(surface->getMaxVisibleDistance(),
							surface_index);
					}

					object->setMinFadeDistance(surface->getMinFadeDistance(), surface_index);
					object->setMaxFadeDistance(surface->getMaxFadeDistance(), surface_index);

					if (ImportMaterialPtr import_material = surface->getMaterial())
					{
						object->setMaterial(materials[import_material->getFilepath()],
							surface_index);
					}
				}
			}

			node = object;
		} else
		{
			node = NodeDummy::create();
			node->setWorldTransform(Mat4(import_node->getTransform()));
		}

		node->setName(import_node->getName());

		getImporter()->importNodeChild(getImportProcessor(), node_parent, import_node_parent,
			node, import_node);

		int num_children = import_node->getNumChildren();
		for (int i = 0; i < num_children; ++i)
		{
			NodePtr child;
			convertNode(node, import_node, child, import_node->getChild(i));
			node->addWorldChild(child);
		}
	}

protected:
	// method to be called on mesh processing
	bool onProcessMesh(const MeshPtr &mesh, const ImportMeshPtr &import_mesh) override
	{
		UGUID guid = generate_unique_guid();
		import_mesh->setFilepath(guid.getString());
		meshes.append(import_mesh->getFilepath(), mesh);
		return true;
	}

	// method to be called on mesh animation processing
	bool onProcessAnimation(const MeshPtr &animation, const ImportMeshPtr &import_mesh,
		const ImportAnimationPtr &import_animation) override
	{
		meshes_animations.append(import_mesh->getFilepath(), animation);
		return true;
	}

	// method to be called on texture processing
	bool onProcessTexture(const ImportTexturePtr &import_texture) override
	{
		import_texture->setFilepath(import_texture->getOriginalFilepath());
		return true;
	}

	// method to be called on material processing
	bool onProcessMaterial(const MaterialPtr &material,
		const ImportMaterialPtr &import_material) override
	{
		UGUID guid = generate_unique_guid();
		import_material->setFilepath(guid.getString());
		materials.append(guid.getString(), material);
		return true;
	}

private:
	UGUID generate_unique_guid()
	{
		UGUID guid;
		guid.generate();

		int attempt = 100;
		while(guids.contains(guid) && attempt-- > 0)
			guid.generate();

		guids.append(guid);

		return guid;
	}

private:
	HashSet<UGUID> guids;
	// HashMaps to be used for imported meshes and materials
	HashMap<String, MeshPtr> meshes;
	HashMap<String, MeshPtr> meshes_animations;
	HashMap<String, MaterialPtr> materials;

};

Using Our Import Processor使用导入处理器#

我们有定制导入处理器,让我们写一个函数使用导入的内容指定的FBX当前加载UNIGINE世界。

导入函数

源代码 (C++)
/// function importing the contents of the specified FBX to the currently loaded scene
NodePtr import(const char *filepath)
{
	// creating an importer for our fbx file
	ImporterPtr importer = Import::createImporterByFileName(filepath);
	if (!importer)
		return nullptr;

	if (!importer->init(filepath))
		return nullptr;

	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScenePtr scene = importer->getScene();
	int num_meshes = scene->getNumMeshes();
	for (int i = 0; i < num_meshes; ++i)
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(i));
		if (m->getNumBones())
		{
			int num_animations = scene->getNumAnimations();
			for (int i = 0; i < num_animations; ++i)
			{
				MeshPtr animation_mesh = Mesh::create();
				importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
					scene->getMesh(i), scene->getAnimation(i));
			}
		}
	}

	int num_textures = scene->getNumTextures();
	for (int i = 0; i < num_textures; ++i)
	{
		importer->importTexture(memory_processor.getImportProcessor(), scene->getTexture(i));
	}
	
	MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");
	int num_materials = scene->getNumMaterials();
	for (int i = 0; i < num_materials; ++i)
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(memory_processor.getImportProcessor(), m, scene->getMaterial(i));
	}

	int num_nodes = scene->getNumNodes();
	ImportNodePtr root_node;
	for (int i = 0; i < num_nodes; ++i)
	{
		const ImportNodePtr &node = scene->getNode(i);
		if (node->getParent() == nullptr)
		{
			root_node = node;
			break;
		}
	}

	if (root_node)
	{
		NodePtr node;
		memory_processor.convertNode(nullptr, nullptr, node, root_node);
		return node;
	}

	return nullptr;
}

现在我们只需加载FbxImporter插件,并使用import()函数添加所需的模型。

源代码 (C++)
// including necessary libraries
#include <UnigineConsole.h>

// ...

// loading the FbxImporter plugin
Console::run("plugin_load FbxImporter");
Console::flush();
	
// importing an FBX-model directly to the scene
NodePtr imported_node = import("material_ball.fbx");

Example Code示例代码#

为了测试我们的自定义导入处理器,我们可以SDK Browser中创建一个新的c++项目,并将下面列出的所有代码放到AppWorldLogic.hAppWorldLogic.cpp文件中。

AppWorldLogic.h

源代码 (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>

class AppWorldLogic : public Unigine::WorldLogic
{

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;
	int postUpdate() override;
	int updatePhysics() override;

	int shutdown() override;

	int save(const Unigine::StreamPtr &stream) override;
	int restore(const Unigine::StreamPtr &stream) override;

private:
	void run_animation(const Unigine::NodePtr &node);
	void setup_gui();
	void setup_player(const Unigine::NodePtr &node);
	void import_file(const Unigine::String &filepath);
	void dialog_file_ok_clicked(Unigine::WidgetPtr, Unigine::WidgetPtr, int, Unigine::WidgetDialogFilePtr dialog);
	void dialog_file_cancel_clicked(Unigine::WidgetPtr, Unigine::WidgetPtr, int, Unigine::WidgetDialogFilePtr dialog);
	void request_import(Unigine::WidgetPtr sender);

	Unigine::NodePtr node_;
	Unigine::String default_dialog_path{"./"};
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cpp

源代码 (C++)
#include "AppWorldLogic.h"
#include <UnigineMathLib.h>
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineMeshStatic.h>
#include <UnigineGame.h>
#include <UnigineWindowManager.h>
using namespace Unigine;
// our custom ImportProcessor to be used for direct memory import of scenes
class MemoryProcessor : public ImportProcessor
{
public:

	void convertNode(const NodePtr &node_parent, const ImportNodePtr &import_node_parent,
		NodePtr &node, const ImportNodePtr &import_node)
	{
		using namespace Unigine::Math;
		if (ImportCameraPtr import_camera = import_node->getCamera())
		{
			node = getImporter()->importCamera(getImportProcessor(), import_camera);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportLightPtr import_light = import_node->getLight())
		{
			node = getImporter()->importLight(getImportProcessor(), import_light);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportMeshPtr import_mesh = import_node->getMesh())
		{
			ObjectPtr object;
			if (import_mesh->isHasAnimations())
			{
				float fps = getImporter()->getParameterFloat("fps");
				auto mesh_skinned = ObjectMeshSkinned::create(meshes[import_mesh->getFilepath()]);
				if (MeshPtr animation = meshes_animations.value(import_mesh->getFilepath()))
				{
					int animation_id = mesh_skinned->addAnimation(animation);
					mesh_skinned->setNumLayers(1);
					mesh_skinned->setAnimation(0, animation_id);
				}
				mesh_skinned->setSpeed(fps);
				object = mesh_skinned;
			} else
			{
				auto mesh_static = ObjectMeshStatic::create();
				mesh_static->setMeshProceduralMode(true);
				mesh_static->applyMeshProcedural(meshes[import_mesh->getFilepath()]);

				object = mesh_static;
				object->setWorldTransform(Mat4(import_node->getTransform()));
			}

			int num_geometries = import_mesh->getNumGeometries();
			for (int i = 0; i < num_geometries; ++i)
			{
				ImportGeometryPtr geometry = import_mesh->getGeometry(i);
				int num_surfaces = geometry->getNumSurfaces();
				for (int s = 0; s < num_surfaces; ++s)
				{
					ImportSurfacePtr surface = geometry->getSurface(s);
					const int surface_index = surface->getTargetSurface();
					if (surface_index == -1 || surface_index >= object->getNumSurfaces())
					{
						Log::error("MemoryProcessor: can't find surface \"%s\".\n", surface->getName());
						continue;
					}

					if (!compare(surface->getMinVisibleDistance(), -Consts::INF))
					{
						object->setMinVisibleDistance(surface->getMinVisibleDistance(),
							surface_index);
					}
					if (!compare(surface->getMaxVisibleDistance(), Consts::INF))
					{
						object->setMaxVisibleDistance(surface->getMaxVisibleDistance(),
							surface_index);
					}

					object->setMinFadeDistance(surface->getMinFadeDistance(), surface_index);
					object->setMaxFadeDistance(surface->getMaxFadeDistance(), surface_index);

					if (ImportMaterialPtr import_material = surface->getMaterial())
					{
						object->setMaterial(materials[import_material->getFilepath()],
							surface_index);
					}
				}
			}

			node = object;
		} else
		{
			node = NodeDummy::create();
			node->setWorldTransform(Mat4(import_node->getTransform()));
		}

		node->setName(import_node->getName());

		getImporter()->importNodeChild(getImportProcessor(), node_parent, import_node_parent,
			node, import_node);

		int num_children = import_node->getNumChildren();
		for (int i = 0; i < num_children; ++i)
		{
			NodePtr child;
			convertNode(node, import_node, child, import_node->getChild(i));
			node->addWorldChild(child);
		}
	}

protected:
	// method to be called on mesh processing
	bool onProcessMesh(const MeshPtr &mesh, const ImportMeshPtr &import_mesh) override
	{
		UGUID guid = generate_unique_guid();
		import_mesh->setFilepath(guid.getString());
		meshes.append(import_mesh->getFilepath(), mesh);
		return true;
	}

	// method to be called on mesh animation processing
	bool onProcessAnimation(const MeshPtr &animation, const ImportMeshPtr &import_mesh,
		const ImportAnimationPtr &import_animation) override
	{
		meshes_animations.append(import_mesh->getFilepath(), animation);
		return true;
	}

	// method to be called on texture processing
	bool onProcessTexture(const ImportTexturePtr &import_texture) override
	{
		import_texture->setFilepath(import_texture->getOriginalFilepath());
		return true;
	}

	// method to be called on material processing
	bool onProcessMaterial(const MaterialPtr &material,
		const ImportMaterialPtr &import_material) override
	{
		UGUID guid = generate_unique_guid();
		import_material->setFilepath(guid.getString());
		materials.append(guid.getString(), material);
		return true;
	}

private:
	UGUID generate_unique_guid()
	{
		UGUID guid;
		guid.generate();

		int attempt = 100;
		while(guids.contains(guid) && attempt-- > 0)
			guid.generate();

		guids.append(guid);

		return guid;
	}

private:
	HashSet<UGUID> guids;
	// HashMaps to be used for imported meshes and materials
	HashMap<String, MeshPtr> meshes;
	HashMap<String, MeshPtr> meshes_animations;
	HashMap<String, MaterialPtr> materials;

};
/// function importing the contents of the specified FBX to the currently loaded scene
NodePtr import(const char *filepath)
{
	// creating an importer for our fbx file
	ImporterPtr importer = Import::createImporterByFileName(filepath);
	if (!importer)
		return nullptr;

	if (!importer->init(filepath))
		return nullptr;

	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScenePtr scene = importer->getScene();
	int num_meshes = scene->getNumMeshes();
	for (int i = 0; i < num_meshes; ++i)
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(i));
		if (m->getNumBones())
		{
			int num_animations = scene->getNumAnimations();
			for (int i = 0; i < num_animations; ++i)
			{
				MeshPtr animation_mesh = Mesh::create();
				importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
					scene->getMesh(i), scene->getAnimation(i));
			}
		}
	}

	int num_textures = scene->getNumTextures();
	for (int i = 0; i < num_textures; ++i)
	{
		importer->importTexture(memory_processor.getImportProcessor(), scene->getTexture(i));
	}
	
	MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");
	int num_materials = scene->getNumMaterials();
	for (int i = 0; i < num_materials; ++i)
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(memory_processor.getImportProcessor(), m, scene->getMaterial(i));
	}

	int num_nodes = scene->getNumNodes();
	ImportNodePtr root_node;
	for (int i = 0; i < num_nodes; ++i)
	{
		const ImportNodePtr &node = scene->getNode(i);
		if (node->getParent() == nullptr)
		{
			root_node = node;
			break;
		}
	}

	if (root_node)
	{
		NodePtr node;
		memory_processor.convertNode(nullptr, nullptr, node, root_node);
		return node;
	}

	return nullptr;
}

AppWorldLogic::AppWorldLogic(){}

AppWorldLogic::~AppWorldLogic(){}

int AppWorldLogic::init()
{
	setup_gui();
	return 1;
}

void AppWorldLogic::run_animation(const NodePtr &node)
{
	if (ObjectMeshSkinnedPtr skinned = checked_ptr_cast<ObjectMeshSkinned>(node))
	{
		skinned->setLoop(1);
		skinned->play();
	}
	
	int num_children = node->getNumChildren();
	for (int i = 0; i < num_children; ++i)
		run_animation(node->getChild(i));
}

void AppWorldLogic::setup_gui()
{
	auto gui = Gui::getCurrent();

	auto window = WidgetWindow::create(gui, "Import File", 4, 4);

	auto import_button = WidgetButton::create(gui, "Import");
	import_button->addCallback(Gui::CLICKED,
	MakeCallback(this, &AppWorldLogic::request_import));

	window->addChild(import_button, Gui::ALIGN_EXPAND);
	window->arrange();

	gui->addChild(window, Gui::ALIGN_OVERLAP | Gui::ALIGN_LEFT | Gui::ALIGN_TOP);
}

void AppWorldLogic::setup_player(const NodePtr &node)
{
	static const float CAMERA_PHI = 75.0f;
	static const float CAMERA_THETA = 140.0f;
	static const float CAMERA_DISTANCE = 2.0f;
	static const float FOV = 60.0f;

	using namespace Unigine::Math;

	ivec2 window_size = WindowManager::getMainWindow()->getClientSize();

	WorldBoundSphere bound_sphere = node->getHierarchyBoundSphere();

	Vec3 center = bound_sphere.center;
	Scalar radius = bound_sphere.radius * CAMERA_DISTANCE *
		max(1.0f, float(window_size.y) / window_size.y);
	quat rotation= quat(1.0f, 0.0f, 0.0f, -CAMERA_PHI) * quat(0.0f, 0.0f, 1.0f, CAMERA_THETA);

	Mat4 modelview{translate(Scalar(0.0f), Scalar(0.0f), -radius) *
		Mat4(rotation) * translate(-center)};

	mat4 projection = perspective(FOV, 1.0f, radius * 0.01f, radius * 2.0f);
	if (PlayerPtr player = Game::getPlayer())
	{
		player->setWorldTransform(inverse(modelview));
		player->setProjection(projection);
	} else
		Log::error("Main player not found.\n");
}

void AppWorldLogic::import_file(const String &filepath)
{
	if (node_)
	{
		node_.deleteLater();
		node_ = nullptr;
	}

	node_ = import(filepath);
	if (node_)
	{
		Log::message("Node loaded \"%s\" from filepath \"%s\".\n",
			node_->getName(), filepath.get());
		setup_player(node_);
		run_animation(node_);
	}
	else
		Log::error("Can't import file from filepath %s\n", filepath.get());
}

void AppWorldLogic::dialog_file_ok_clicked(WidgetPtr, WidgetPtr, int, WidgetDialogFilePtr dialog)
{
	const String filepath = dialog->getFile();
	default_dialog_path = filepath.pathname();

	import_file(filepath);

	dialog.deleteLater();
}

void AppWorldLogic::dialog_file_cancel_clicked(WidgetPtr, WidgetPtr, int, WidgetDialogFilePtr dialog)
{
	dialog.deleteLater();
}

void AppWorldLogic::request_import(WidgetPtr sender)
{
	const Vector<String> &supported_extensions = Import::getSupportedExtensions();
	const String extensions_filter = "." + String::join(supported_extensions, ".");

	auto dialog_file = WidgetDialogFile::create(Gui::getCurrent(), "DialogFile");
	dialog_file->setPath(default_dialog_path);
	dialog_file->setFilter(extensions_filter);

	dialog_file->getOkButton()->addCallback(Gui::CLICKED,
		MakeCallback(this, &AppWorldLogic::dialog_file_ok_clicked, dialog_file));
	dialog_file->getCancelButton()->addCallback(Gui::CLICKED,
		MakeCallback(this, &AppWorldLogic::dialog_file_cancel_clicked, dialog_file));

	Gui::getCurrent()->addChild(dialog_file, Gui::ALIGN_OVERLAP | Gui::ALIGN_CENTER);

	dialog_file->setPermanentFocus();
}

////////////////////////////////////////////////////////////////////////////////
// start of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::update()
{
	// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.
	return 1;
}

int AppWorldLogic::postUpdate()
{
	// The engine calls this function after updating each render frame: correct behavior after the state of the node has been updated.
	return 1;
}

int AppWorldLogic::updatePhysics()
{
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
	// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// end of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::shutdown()
{
	// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is saving its state (i.e. state_save is called): save custom user data to a file.
	UNIGINE_UNUSED(stream);
	return 1;
}

int AppWorldLogic::restore(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is restoring its state (i.e. state_restore is called): restore custom user data to a file here.
	UNIGINE_UNUSED(stream);
	return 1;
}
最新更新: 2023-12-19
Build: ()