This page has been translated automatically.
Видеоуроки
Интерфейс
Основы
Продвинутый уровень
Подсказки и советы
Основы
Программирование на C#
Рендеринг
Профессиональный уровень (SIM)
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Физика
Редактор UnigineEditor
Обзор интерфейса
Работа с ассетами
Контроль версий
Настройки и предпочтения
Работа с проектами
Настройка параметров ноды
Setting Up Materials
Настройка свойств
Освещение
Sandworm
Использование инструментов редактора для конкретных задач
Расширение функционала редактора
Встроенные объекты
Ноды (Nodes)
Объекты (Objects)
Эффекты
Декали
Источники света
Geodetics
World-ноды
Звуковые объекты
Объекты поиска пути
Player-ноды
Программирование
Основы
Настройка среды разработки
Примеры использования
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Плагины
Форматы файлов
Материалы и шейдеры
Rebuilding the Engine Tools
Интерфейс пользователя (GUI)
Двойная точность координат
API
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
Учебные материалы

Краткий справочник по программированию

Введение#

В настоящем справочнике вы найдете основную информацию о том, как начать программировать 3D-приложения и видеоигры с помощью UNIGINE. Справочник включает в себя следующие главы:

  1. Основные объекты сцены
  2. Система координат
  3. Ведение журнала и вывод сообщений в консоль
  4. Сохранение и загрузка мира
  5. Закрытие приложения
  6. Создание и удаление объектов во время выполнения
  7. Создание и настройка камеры
  8. Создание и настройка источников света
  9. Создание, применение и удаление материалов во время выполнения
  10. Управление существующими объектами сцены
  11. Выполнение базовых преобразований (перемещение, поворот, масштабирование)
  12. Как сделать игровой процесс независимым от частоты кадров
  13. Управление пересечениями
  14. Получение пользовательских вводов и управление ими
  15. Создание пользовательского интерфейса
  16. Воспроизведение звука и музыки
  17. Настройка физики
  18. Обнаружение объектов с помощью World Trigger

Основные объекты сцены#

В UNIGINE нода (node) — это базовый тип, от которого наследуются все типы объектов сцены. Некоторые из них отображаются визуально: объекты, декали и эффекты — все они имеют поверхности для представления их геометрии (меш), в то время как другие ( Источники света, Персонажи и т.д.) невидимы.

Каждая нода имеет матрицу преобразования, которая кодирует положение, поворот и масштаб ноды в мире.

Все объекты сцены, добавленные в сцену, независимо от их типа, называются нодами.

Дополнительная информация:

  • Дополнительные сведения о типах объектов UNIGINE см. в разделе Встроенные типы объектов.
  • Для получения дополнительной информации об управлении нодами с помощью API см. раздел Node-классы.

Система координат#

Трехмерное пространство в UNIGINE представлено правой декартовой системой координат: оси X и Y образуют горизонтальную плоскость, ось Z направлена вверх. При экспорте анимации из 3D-редакторов Y считается прямым направлением.

Система координат

Положительный угол поворота задает вращение против часовой стрелки. Это соответствует правилу правой руки: если вы установите большой палец правой руки вдоль оси, другие обернутые пальцы будут показывать направление вращения.

Направления вращения

Дополнительная информация:

  1. Дополнительные сведения о типах объектов UNIGINE см. в разделе Встроенные типы объектов.
  2. Для получения дополнительной информации об управлении нодами с помощью API см. раздел Node-классы.

Ведение журнала и вывод сообщений в консоль#

Вывод сообщений в файл журнала и консоль помогает отслеживать общий ход выполнения вашего приложения и сообщать об ошибках, которые могут быть использованы при отладке. Класс Log позволяет печатать форматированные строковые сообщения в файл журнала и консоль. Приведенный ниже код демонстрирует, как выводить различные типы сообщений:

Примечание
Чтобы включить отображение сообщений в оверлее экрана, используйте следующую команду: console_onscreen 1
Исходный код (C++)
using namespace Unigine;

// auxiliary variables for messages
const char* file_name = "file.txt";
int ID = 10;

	// reporting an error message
	Log::error("Loading mesh: can't open \"%s\" file\n", file_name);

	// reporting a message
	Log::message("-> Added %d UI elements.\n", ID);

	// reporting a warning message
	Log::warning("ID of the \"%s\" file: %d.\n", file_name, ID);

	// reporting a fatal error message to the log file and closing the application
	Log::fatal("FATAL ERROR reading \"%s\" file!\n", file_name);

Дополнительная информация:

  • Для получения дополнительной информации о консоли см. статью Console.
  • Для получения дополнительной информации о классе Log см. статью о классе Log.

Сохранение и загрузка мира#

Некоторые приложения управляют одним миром, в то время как другие требуют управления несколькими мирами. В любом случае, очень полезно знать, как сохранить наш текущий мир и загрузить какой-нибудь другой. Чтобы решить эту задачу, мы должны использовать класс World, который разработан как синглтон.

Примечание
Чтобы использовать класс World, включите библиотеку UnigineWorld.h.
Исходный код (C++)
#include <UnigineWorld.h>

using namespace Unigine;
/* .. */

// loading world from the my_world.world file
World::loadWorld("my_world");

Мы также можем сделать то же самое через консоль, используя класс Console, который также разработан как синглтон.

Примечание
Чтобы использовать класс Console, включите библиотеку UnigineConsole.h.
Исходный код (C++)
#include <UnigineConsole.h>

using namespace Unigine;
/* .. */

// saving current world to the my_world.world file
Console::run("world_save my_world");

// loading world from the my_world.world file
Console::run("world_load my_world");

Дополнительная информация:

  • Для получения дополнительной информации об управлении мирами с помощью API см. статью о классе World.
  • Для получения дополнительной информации о консоли и доступных командах см. статью Консоль.
  • Для получения дополнительной информации об управлении консолью через API см. статью о классе Console.
  • Для получения дополнительной информации об управлении мировыми нодами, которые должны быть сохранены с помощью API, см. методы класса Node.

Закрытие приложения#

Любое приложение должно быть закрыто в какой-то момент. Чтобы закрыть ваше приложение, используйте класс Engine.

Чтобы закрыть приложение, необходимо использовать следующий код:

Исходный код (C++)
using namespace Unigine;

/* .. */
		
// closing the application 
Engine::get()->quit();

Создание и удаление нод во время выполнения#

Ноды можно создавать и удалять во время выполнения почти так же просто, как в редакторе. Основной набор действий заключается в следующем:

  • Создание. Чтобы создать ноду, мы должны объявить интеллектуальный указатель для типа ноды, которую мы собираемся создать, и вызвать конструктор соответствующего класса, при необходимости предоставляя параметры построения.
  • Удаление. Чтобы удалить ноду, мы просто вызываем метод deleteLater() для ноды, которую мы собираемся удалить.
Исходный код (C++)
// creating a node of the NodeType named nodename
<NodeType>Ptr nodename = <NodeType>::create(<construction_parameters>);
	
// removing the node
nodename.deleteLater();

Теперь давайте проиллюстрируем процесс создания и удаления ноды на примере простого статического меша (ObjectMeshStatic) с использованием геометрии загружаемой из ассета.

Исходный код (C++)
int AppWorldLogic::init()
{

	//create an ObjectMeshStatic node from an fbx model imported to the data/fbx/ folder using the Editor
	ObjectMeshStaticPtr my_object = ObjectMeshStatic::create("fbx/model.fbx/model.mesh");

	// removing the node
	my_object.deleteLater();

	return 1;
}

Дополнительная информация:

  • Вы можете создавать примитивы с помощью кода, используя класс Primitives.
  • Для получения дополнительной информации об управлении мировыми нодами см. методы класса Node.

Создание и настройка камеры#

Камера — это окно в мир, без него вы на самом деле ничего не сможете увидеть. Камеры в UNIGINE управляются с помощью Player. Когда вы добавляете нового персонажа, он создает камеру и задает элементы управления, маски, материалы для последующей обработки для этой камеры.

Для того, чтобы установить нового персонажа в качестве активного, мы должны использовать класс Game, который разработан как синглтон.

Примечание
Чтобы использовать класс Game и класс PlayerSpectator, мы должны включить библиотеки UnigineGame.h и UniginePlayers.h.

Следующий код иллюстрирует создание PlayerSpectator и установку его в качестве активной игровой камеры.

Примечание
По умолчанию персонаж использует стандартный набор элементов управления, которые при необходимости могут быть переопределены.
Исходный код (C++)
#include <UnigineGame.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

int AppWorldLogic::init()
{

	// creating a new PlayerSpectator instance
	PlayerSpectatorPtr playerSpectator = PlayerSpectator::create();

	// setting necessary parameters: FOV, ZNear, ZFar, view direction vector and position.
	playerSpectator->setFov(90.0f);
	playerSpectator->setZNear(0.1f);
	playerSpectator->setZFar(10000.0f);
	playerSpectator->setViewDirection(Math::vec3(0.0f, 1.0f, 0.0f));
	playerSpectator->setWorldPosition(Math::Vec3(-1.6f, -1.7f, 1.7f));

	// setting the player as a default one via the Game singleton instance
	Game::setPlayer(playerSpectator);

	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации об игроках см. статью Персонажи.
  • Для получения дополнительной информации об API игроков см. статью Player-классы.
  • Для получения дополнительной информации о игровом классе см. статью о классе Game.

Создание и настройка источников света#

Освещение является основой каждой сцены, определяющей цвета и окончательный вид ваших объектов. Источники света в UNIGINE создаются так же, как и все остальные ноды.

Давайте рассмотрим создание мирового источника света в качестве примера:

Исходный код (C++)
#include <UnigineLights.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

int AppWorldLogic::init()
{

	// creating a world light source and setting its color to white
	LightWorldPtr sun = LightWorld::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f));

	// setting light source's parameters (intensity, disable angle, scattering type, name and rotation)
	sun->setName("Sun");
	sun->setDisableAngle(90.0f);
	sun->setIntensity(1.0f);
	sun->setScattering(LightWorld::SCATTERING::SCATTERING_SUN);
	sun->setWorldRotation(Math::quat(86.0f, 30.0f, 300.0f));

	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации об источниках света см. статью Источники света.
  • Для получения дополнительной информации об API источников света см. статью Lights-классы.

Создание, применение и удаление материалов во время выполнения#

Материалы, назначенные определенным поверхностям объекта, определяют, как будет отображаться объект. Они реализуют шейдеры и управляют тем, какие опции, состояния, параметры различных типов и текстуры используются для рендеринга объекта во время прохода рендеринга. Для управления материалами мы используем следующие два класса:

  • Класс Materials, который представляет интерфейс для управления загруженными материалами.
  • Класс Material, который используется для управления каждым отдельным материалом.
Примечание
Для того, чтобы использовать материалы, мы должны включить библиотеку UnigineMaterials.h

Следующий код можно использовать для создания нового материала, унаследованного от материала mesh_base.

Исходный код (C++)
#include <UnigineMaterials.h>
#include <UniginePrimitives.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

int AppWorldLogic::init()
{

	// creating a box (ObjectMeshDynamic node)
	ObjectMeshDynamicPtr my_mesh = Primitives::createBox(Math::vec3(1.5f, 1.5f, 1.5f));

	// getting the base mesh_base material to inherit from
	MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");

	// inherit a new child material from it
	MaterialPtr my_mesh_base = mesh_base->inherit();

	// save it to  "materials/my_mesh_base0.mat"
	my_mesh_base->createMaterialFile("materials/my_mesh_base0.mat");

	// setting the albedo color of the material to red
	my_mesh_base->setParameterFloat4("albedo_color", Math::vec4(255, 0, 0, 255));

	// assigning a "my_mesh_base0.mat" material to the surface 0 of the my_mesh ObjectMeshDynamic node
	my_mesh->setMaterialPath("materials/my_mesh_base0.mat", 0);

	// assigning a "my_mesh_base0.mat" material to all surfaces of the my_mesh ObjectMeshDynamic node
	my_mesh->setMaterialPath("materials/my_mesh_base0.mat", "*");

	return 1;
}

int AppWorldLogic::shutdown()
{

	// deleting the material "materials/my_mesh_base0.mat"
	Materials::removeMaterial(Materials::findMaterialByPath("materials/my_mesh_base0.mat")->getGUID());
	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации о создании и редактировании материалов с помощью API см. статью о классе Material.
  • Для получения дополнительной информации об управлении загруженными материалами с помощью API см. статью о классе Materials.
  • Для получения дополнительной информации о форматах файлов материалов см. раздел Файлы материалов.
  • Для получения дополнительной информации о параметрах материалов см. файлы материалов в папке %UNIGINE_SDK_BROWSER_INSTALLATION_FOLDER%/sdks/%CURRENT_SDK%/data/core/materials/default/.

Управление существующими объектами сцены#

Не весь контент в мире создается во время выполнения, поэтому мы должны иметь возможность работать с уже существующими нодами. Как мы получаем указатели на существующие объекты, чтобы управлять ими? Здесь снова вступает в игру класс World. В принципе, есть два способа, которыми мы можем получить указатель на определенную ноду, используя методы класса World:

  • метод getNodeByName() — когда мы знаем имя ноды
  • метод getNodeByID() — когда мы знаем идентификатор ноды
Примечание
Для того, чтобы иметь возможность использовать методы класса World, мы должны включить библиотеку UnigineWorld.h.

Эти методы возвращают значение NodePtr, которое является указателем на базовый класс, но для выполнения операций с определенным объектом (например, ObjectMeshDynamicPtr) нам необходимо выполнить понижающее преобразование (т.е. преобразовать указатель на базовый класс в указатель на производный).

Иногда вам также может потребоваться выполнить upcasting (т.е. преобразовать указатель на производный в указатель на базовый), в этом случае вы можете использовать сам производный класс. Приведенный ниже код демонстрирует моменты, описанные выше.

Исходный код (C++)
#include <UnigineWorld.h>
using namespace Unigine;
/* .. */

// find a pointer to node by a given name
NodePtr baseptr = World::getNodeByName("my_meshdynamic");

// cast a pointer-to-derived from pointer-to-base with automatic type checking
ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);

// static cast (pointer-to-derived from pointer-to-base)
ObjectMeshDynamicPtr derivedptr = static_ptr_cast<ObjectMeshDynamic>(World::getNodeByName("my_meshdynamic"));

// upcast to the pointer to the Object class which is a base class for ObjectMeshDynamic
ObjectPtr object = derivedptr;

// upcast to the pointer to the Node class which is a base class for all scene objects
NodePtr node = derivedptr;

Существуют следующие способы получения компонента для ноды:

Исходный код (C++)
// get the component assigned to a node by type "MyComponent"
MyComponent* my_component = ComponentBase::getComponent<MyComponent>(node);

// // do the same by using the function of the Component System
MyComponent* my_component = ComponentSystem::get()->getComponent<MyComponent>(color_zone);

Дополнительная информация:

  • Дополнительные сведения о работе с умными указателями см. в статье Работа с умными указателями.
  • Для получения дополнительной информации см. статью о классе World.

Выполнение базовых преобразований (перемещение, поворот, масштабирование)#

Каждая нода имеет матрицу преобразования, которая кодирует положение, поворот и масштаб ноды в мире. Если нода добавляется как дочерняя для другой ноды, она имеет матрицу преобразования, связанную с ее родительской нодой. Вот почему класс Node имеет разные функции: getTransform(), setTransform() и getWorldTransform(), setWorldTransform(), которые работают с локальными и мировыми матрицами преобразования соответственно. Следующий код иллюстрирует, как выполнять базовые преобразования нод:

Исходный код (C++)
// injecting Unigine namespace to the global namespace
using namespace Unigine;
using namespace Unigine::Math;

// move the node by X, Y, Z units along the corresponding axes
node->setWorldPosition(node->getWorldPosition() + Vec3(X, Y, Z));

// move the node by one unit along the Y axis
node->worldTranslate(0.0f, 1.0f, 0.0f);

// rotate the node around the axis (X, Y, Z) by the Alpha angle 
node->setWorldRotation(node->getWorldRotation() * quat(Vec3(X, Y, Z), Alpha));

// rotate the node around X, Y, and Z axes by the corresponding angle (angle_X, angle_Y, angle_Z)
node->setWorldRotation(node->getWorldRotation() * quat(angle_X, angle_Y, angle_Z));

// rotate the node by 45 degrees along the Z axis
node->worldRotate(0.0f, 0.0f, 45.0f);

// orient the node using a direction vector and a vector pointing upwards
node->setWorldDirection(vec3(0.5f, 0.5f, 0.0f), vec3_up, AXIS_Y);

// setting node scale to Scale_X, Scale_Y, Scale_Z along the corresponding axes
node->setWorldScale(vec3(Scale_X, Scale_Y, Scale_Z));

// setting new transformation matrix to scale the node 2 times along all axes, rotate it by 45 degrees around the Z-axis and move it by 1 unit along all axes
Mat4 transform = Mat4(translate(vec3(1.0f, 1.0f, 1.0f)) * rotate(quat(0.0f, 0.0f, 1.0f, 45.0f)) * scale(vec3(2.0f)));

// setting node transformation matrix relative to its parent
node->setTransform(transform);

// setting node transformation matrix relative to the world origin
node->setWorldTransform(transform);

Дополнительная информация:

Сделать игровой процесс независимым от частоты кадров#

Поскольку частота кадров нашего приложения может варьироваться (т.е. метод AppWorldLogic::update() будет вызываться более или менее часто) в зависимости от аппаратного обеспечения, мы должны что-то сделать, чтобы гарантировать, что определенные действия выполняются в одни и те же периоды времени независимо от частоты кадров (например, изменять что-то один раз в секунду и т.д.). Чтобы сделать частоту кадров вашей игры независимой, вы можете использовать множитель масштабирования (время в секундах, затраченное на завершение последнего кадра), возвращаемый следующими методами:

  • Engine::getIfps() возвращает обратное значение FPS для вашего приложения.
  • Game::getIfps() возвращает масштабированное обратное значение FPS. Этот класс предназначен для использования, когда вы хотите ускорить, замедлить или приостановить рендеринг, физику или игровую логику.

Чтобы изменить преобразования, вы можете использовать следующий код:

Исходный код (C++)
#include <UnigineGame.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

int AppWorldLogic::update()
{

	// getting an inverse FPS value (the time in seconds it took to complete the last frame)
	float ifps = Game::getIFps();

	// moving the node up by 0.3 units every second instead of every frame
	node->worldTranslate(Math::Vec3(0.0f, 0.0f, 0.3f * ifps));

	return 1;
}

Чтобы выполнить некоторые изменения один раз за определенный промежуток времени, вы можете использовать следующий код:

Исходный код (C++)
#include <UnigineGame.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

const float INTERVAL_DURATION = 5;		// interval duration
float elapsed_time = INTERVAL_DURATION;	// current time left to make changes

int AppWorldLogic::update()
{

	// getting an inverse FPS value (the time in seconds it took to complete the last frame)
	float ifps = Game::getIFps();

	// checking if it's time to make changes
	if (elapsed_time < 0.0f)
	{

		/* .. DO SOME CHANGES .. */

		// resetting elapsed time counter
		elapsed_time = INTERVAL_DURATION;
	}

	// decreasing elapsed time counter
	elapsed_time -= ifps;

	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации о классе Game см. статью Game.
  • Для получения дополнительной информации о классе Engine см. статью Engine.

Управление пересечениями#

Пересечения широко используются в 3D-приложениях. В UNIGINE существует три основных типа пересечений:

Но есть некоторые условия для обнаружения пересечений с поверхностью:

  1. Поверхность включена.
  2. Поверхности присвоен материал.
  3. Для каждой поверхности включен флаг Intersection.

    Примечание
    Вы можете установить этот флаг на поверхность объекта с помощью функции Object.setIntersection().

Приведенный ниже код иллюстрирует несколько способов использования мировых пересечений:

  • чтобы найти все ноды, пересекаемые ограничивающим прямоугольником;
  • чтобы найти все ноды, пересекаемые ограничивающей сферой;
  • чтобы найти все ноды, пересекаемые ограничивающим усеченным конусом;
  • чтобы найти первый объект, пересеченный лучом.
Исходный код (C++)
#include <UnigineGame.h>

#include <UnigineWorld.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

int listNodes(Vector<Ptr<Node>>& nodes, const char* intersection_with)
{
	Log::message("Total number of nodes intersecting a %s is: %i \n", intersection_with, nodes.size());

	for (int i = 0; i < nodes.size(); i++)
	{
		Log::message("Intersected node: %s \n", nodes.get(i)->getName());
	}

	// clearing the list of nodes 
	nodes.clear();

	return 1;
}

int AppWorldLogic::update()
{

	// getting a player pointer
	PlayerPtr player = Game::getPlayer();

	// creating a vector to store intersected nodes
	Vector<Ptr<Node>> nodes;

	//-------------------------- FINDING INTERSECTIONS WITH A BOUNDING BOX -------------------------

	// initializing a bounding box with a size of 3 units, located at the World's origin 
	WorldBoundBox boundBox(Math::Vec3(0.0f), Math::Vec3(3.0f));

	// finding nodes intersecting a bounding box and listing them if any
	if (World::getIntersection(boundBox, nodes))
		listNodes(nodes, "bounding box");

	//------------------------- FINDING INTERSECTIONS WITH A BOUNDING SPHERE ------------------------

	// initializing a bounding sphere with a radius of 3 units, located at the World's origin 
	WorldBoundSphere boundSphere(Math::Vec3(0.0f), 3.0f);

	// finding nodes intersecting a bounding sphere and listing them if any
	if (World::getIntersection(boundSphere, nodes))
		listNodes(nodes, "bounding sphere");

	//------------------------- FINDING INTERSECTIONS WITH A BOUNDING FRUSTUM -----------------------

	// initializing a bounding frustum with a  frustum of the player's camera
	WorldBoundFrustum boundFrustum(player->getCamera()->getProjection(), player->getCamera()->getModelview());

	// finding ObjectMeshStaticNodes intersecting a bounding frustum and listing them if any
	if (World::getIntersection(boundFrustum, Node::OBJECT_MESH_STATIC, nodes))
		listNodes(nodes, "bounding frustum");

	//---------------- FINDING THE FIRST OBJECT INTERSECTED BY A RAY CAST FROM P0 to P1 --------------

	// initializing points of the ray from player's position in the direction pointed by the mouse cursor 
	Math::ivec2 mouse = Input::getMousePosition();
	Math::Vec3 p0 = player->getWorldPosition();
	Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100;

	//creating a WorldIntersection object to store the information about the intersection
	WorldIntersectionPtr intersection = WorldIntersection::create();

	// casting a ray from p0 to p1 to find the first intersected object
	ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection);

	// print the name of the first intersected object and coordinates of intersection point if any
	if (obj)
	{
		Math::Vec3 p = intersection->getPoint();
		Log::message("The first object intersected by the ray at point (%f, %f, %f) is: %s \n ", p.x, p.y, p.z, obj->getName());
	}

	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации о пересечениях см. статью Пересечения.
  • Для получения дополнительной информации об управлении пересечениями World с помощью API см. статью о классе World.
  • Для получения дополнительной информации об управлении пересечениями Game с помощью API см. статью о классе Game.
  • Для получения дополнительной информации об управлении пересечениями Physics с помощью API см. статью о классе Physics.

Получение пользовательских данных и управление ими#

Большинство приложений предназначены для взаимодействия с пользователем. В UNIGINE вы можете управлять вводимыми пользовательскими данными, используя следующие классы:

Следующий код иллюстрирует, как использовать класс Input для получения координат мыши в случае, если была нажата правая кнопка мыши, и для закрытия приложения, если была нажата клавиша "q" (игнорируя эту клавишу, если консоль открыта):

Исходный код (C++)
#include <UnigineInput.h>

#include <UnigineConsole.h>

int AppWorldLogic::update()
{

	// if right mouse button is clicked
	if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
	{
		Math::ivec2 mouse = Input::getMousePosition();
		// report mouse cursor coordinates to the console
		Log::message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
	}

	// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
	if (Input::isKeyDown(Input::KEY_Q) && !Console::isActive())
	{
		Engine::get()->quit();
	}

	return 1;
}

Следующий код иллюстрирует, как использовать класс Controls для обработки ввода с клавиатуры:

Исходный код (C++)
#include <UnigineGame.h>

int AppWorldLogic::update()
{

	// getting current controls
	ControlsPtr controls = Game::getPlayer()->getControls();

	// checking controls states and reporting which buttons were pressed
	if (controls->clearState(Controls::STATE_FORWARD) || controls->clearState(Controls::STATE_TURN_UP))
	{
		Log::message("FORWARD or UP key pressed\n");
	}
	else if (controls->clearState(Controls::STATE_BACKWARD) || controls->clearState(Controls::STATE_TURN_DOWN))
	{
		Log::message("BACKWARD or DOWN key pressed\n");
	}
	else if (controls->clearState(Controls::STATE_MOVE_LEFT) || controls->clearState(Controls::STATE_TURN_LEFT))
	{
		Log::message("MOVE_LEFT or TURN_LEFT key pressed\n");
	}
	else if (controls->clearState(Controls::STATE_MOVE_RIGHT) || controls->clearState(Controls::STATE_TURN_RIGHT))
	{
		Log::message("MOVE_RIGHT or TURN_RIGHT key pressed\n");
	}

	return 1;
}

Следующий код иллюстрирует, как использовать класс ControlsApp для сопоставления клавиш и кнопок с состояниями, а затем для обработки пользовательского ввода:

Исходный код (C++)
#include <UnigineGame.h>

int AppWorldLogic::init()
{

	// remapping states to other keys and buttons
	ControlsApp::setStateKey(Controls::STATE_FORWARD, Input::KEY_PGUP);
	ControlsApp::setStateKey(Controls::STATE_BACKWARD, Input::KEY_PGDOWN);
	ControlsApp::setStateKey(Controls::STATE_MOVE_LEFT, Input::KEY_L);
	ControlsApp::setStateKey(Controls::STATE_MOVE_RIGHT, Input::KEY_R);
	ControlsApp::setStateMouseButton(Controls::STATE_JUMP, Input::MOUSE_BUTTON_LEFT);
	
	return 1;
}

int AppWorldLogic::update()
{

	if (ControlsApp::clearState(Controls::STATE_FORWARD))
	{
		Log::message("FORWARD key pressed\n");
	}
	else if (ControlsApp::clearState(Controls::STATE_BACKWARD))
	{
		Log::message("BACKWARD key pressed\n");
	}
	else if (ControlsApp::clearState(Controls::STATE_MOVE_LEFT))
	{
		Log::message("MOVE_LEFT key pressed\n");
	}
	else if (ControlsApp::clearState(Controls::STATE_MOVE_RIGHT))
	{
		Log::message("MOVE_RIGHT key pressed\n");
	}
	else if (ControlsApp::clearState(Controls::STATE_JUMP))
	{
		Log::message("JUMP button pressed\n");
	}
	
	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации об управлении пользовательскими вводами с помощью класса Input см. статью Input class.
  • Для получения дополнительной информации об управлении пользовательскими вводами с помощью класса Controls см. статью Controls class.
  • Для получения дополнительной информации об управлении пользовательскими вводами с помощью класса ControlsApp см. статью ControlsApp class.

Создание пользовательского интерфейса#

В UNIGINE графический пользовательский интерфейс (GUI) состоит из различных типов виджетов, добавленных к нему. В принципе, существует два способа создания графического интерфейса:

  • Путем добавления виджетов в системный графический интерфейс (пользовательский интерфейс UNIGINE), который отображается поверх окна приложения.
  • Путем добавления виджетов к объекту GUI, расположенному в мире. В этом случае может быть применен любой фильтр постобработки.

Чтобы добавить элементы в системный графический интерфейс, вы должны использовать класс Gui.

Примечание
Чтобы использовать виджеты GUI, мы должны включить библиотеку UnigineUserInterface.h.

Существует 2 способа создания макета графического интерфейса:

Следующий код демонстрирует, как добавить метку и ползунок в системный графический интерфейс:

Исходный код (C++)
#include <UnigineUserInterface.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

GuiPtr gui;

int AppWorldLogic::init()
{

	// getting a GUI pointer
	gui = Gui::getCurrent();

	// creating a label widget and setting up its parameters
	WidgetLabelPtr widget_label = WidgetLabel::create(gui, "Label text:");
	widget_label->setToolTip("This is my label!");
	widget_label->arrange();
	widget_label->setPosition(10, 10);

	// creating a slider widget and setting up its parameters
	WidgetSliderPtr widget_slider = WidgetSlider::create(gui, 0, 360, 90);
	widget_slider->setToolTip("This is my slider!");
	widget_slider->arrange();
	widget_slider->setPosition(100, 10);

	gui->addChild(widget_label, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);
	gui->addChild(widget_slider, Gui::ALIGN_OVERLAP | Gui::ALIGN_FIXED);

	return 1;
}

Чтобы использовать элементы графического интерфейса, мы должны указать обработчики для различных событий (щелчок, изменение и т.д.). Следующий код демонстрирует, как настроить обработчики событий:

Исходный код (C++)
#include <UnigineUserInterface.h>

// injecting Unigine namespace to the global namespace
using namespace Unigine;

GuiPtr gui;

/// function to be called when button1 is clicked
int onButton1Clicked(const WidgetPtr &button)
{
	/* .. */
	}

/// method to be called when button2 is clicked
int AppWorldLogic::onButton2Clicked(const WidgetPtr &button)
{
	/* .. */
	}

/// method to be called when delete button is clicked
int AppWorldLogic::onButtonDelClicked(const WidgetPtr &button)
{
	/* .. */
	}

/// method to be called when slider position is changed
int AppWorldLogic::onSliderChanged(const WidgetPtr &slider)
{
	/* .. */
	}

int AppWorldLogic::init()
{

	/* .. */

	// getting a GUI pointer
	gui = Gui::getCurrent();

	
	// subscbribing for the clicked event with the onButton1Clicked function as a handler
	buttonwidget1->getEventClicked().connect(onButton1Clicked);

	// subscbribing for the clicked event with the onButton2Clicked function as a handler
	buttonwidget2->getEventClicked().connect(this, &AppWorldLogic::onButton2Clicked);

	buttonwidget1->getEventClicked().connect(this, &AppWorldLogic::onButtonDelClicked);

	// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
	widget_slider->getEventChanged().connect(this, &AppWorldLogic::onSliderChanged);
	/* .. */

	return 1;
}

Дополнительная информация:

  • Для получения дополнительной информации о файлах пользовательского интерфейса см. статью Файлы пользовательского интерфейса.
  • Для получения дополнительной информации об API, связанном с графическим интерфейсом, см. раздел GUI-классы.
  • Для получения дополнительной информации о классе Gui см. статью Gui класс.
  • Для получения дополнительной информации об обработке событий см. статью Обработка событий.

Воспроизведение звука и музыки#

Источник звука SoundSource#

Класс SoundSource используется для создания направленных источников звука. Чтобы создать источник звука, создайте экземпляр класса SoundSource и укажите все необходимые настройки:

Исходный код (C++)
// create a new sound source using the given sound sample file
SoundSourcePtr sound = SoundSource::create("sound.mp3");

// disable sound muffling when being occluded
sound->setOcclusion(0);
// set the distance at which the sound gets clear
sound->setMinDistance(10.0f);
// set the distance at which the sound becomes out of audible range
sound->setMaxDistance(100.0f);
// set the gain that result in attenuation of 6 dB
sound->setGain(0.5f);
// loop the sound
sound->setLoop(1);
// start playing the sound sample 
sound->play();

Источник AmbientSource#

Для воспроизведения фоновой музыки создайте экземпляр класса AmbientSource, укажите все необходимые параметры и включите его. Убедитесь, что вы импортировали звуковой ассет в проект.

Примечание
Для воспроизведения окружающего источника всегда требуется персонаж. В случае, если необходимо воспроизвести окружающий источник, когда ни мир, ни редактор не загружены, персонаж, а также источник звука (см. код ниже) должны быть созданы в методе SystemLogic.Init(); в противном случае звука не будет слышно.
Исходный код (C++)
// create a player so that an ambient sound source is played
PlayerSpectatorPtr player = PlayerSpectator::create();
player->setPosition(Vec3(0.0f, -3.401f, 1.5f));
player->setViewDirection(vec3(0.0f, 1.0f, -0.4f));
Game::setPlayer(player);

// create the ambient sound source
AmbientSourcePtr sound = AmbientSource::create("sound.mp3");

// set necessary sound settings
sound->setGain(0.5f);
sound->setPitch(1.0f);
sound->setLoop(1);
sound->play();

Дополнительная информация:

  • Для получения дополнительной информации о направленном звуке см. статью о классе SoundSource.
  • Для получения дополнительной информации об окружающем звуке см. статью о классе AmbientSource.

Настройка физики#

Объект должен иметь тело и форму, чтобы на него воздействовала сила тяжести и он мог сталкиваться с другими физическими объектами:

Исходный код (C++)
// cube 
ObjectMeshStaticPtr box = ObjectMeshStatic::create("core/meshes/box.mesh");

// create a body and a shape based on the mesh
BodyRigidPtr bodyBox = BodyRigid::create(box);
ShapeBoxPtr shapeBox = ShapeBox::create(bodyBox, Math::vec3(1.0f));

Обнаружение объектов с помощью World Trigger#

World trigger генерирует события, когда какие-либо ноды (коллайдеры или нет) попадают внутрь или выходят из него. Триггер может обнаружить ноду любого типа по его ограничительной рамке. Триггер реагирует на все ноды (поведение по умолчанию).

Функция-обработчик события World Trigger фактически выполняется только тогда, когда вызывается следующая функция механизма: то есть перед updatePhysics() (в текущем кадре) или перед update() (в следующем кадре) — независимо от того, что будет первым.

Примечание
Если вы переместили некоторые ноды и хотите, чтобы функции-обработчики выполнились с учетом измененных позиций в том же кадре, вам нужно сначала вызвать updateSpatial().

Вы можете подписаться на события Enter и Leave, указав функции-обработчики, которые будут выполняться, когда нода входит в World Trigger или выходит из него. Функция-обработчик должна получать ноду в качестве своего первого аргумента.

Исходный код (C++)
// implement the enter event handler
void AppWorldLogic::enter_event_handler(const NodePtr &node)
{
	Log::message("\nA node named %s has entered the trigger\n", node->getName());
}

WorldTriggerPtr trigger;

int AppWorldLogic::init()
{

	// create a world trigger node
	trigger = WorldTrigger::create(Math::vec3(3.0f));

	// add the enter event handler to be executed when a node enters the world trigger
	trigger->getEventEnter().connect(this, &AppWorldLogic::enter_event_handler);
	
	// add the leave event handler to be executed when a node leaves the world trigger
	trigger->getEventLeave().connect(this, &AppWorldLogic::leave_event_handler);
	

	return 1;
}
Последнее обновление: 15.12.2023
Build: ()