Краткий справочник по программированию
Введение#
В настоящем справочнике вы найдете основную информацию о том, как начать программировать 3D-приложения и видеоигры с помощью UNIGINE. Справочник включает в себя следующие главы:
- Основные объекты сцены
- Система координат
- Ведение журнала и вывод сообщений в консоль
- Сохранение и загрузка мира
- Закрытие приложения
- Создание и удаление объектов во время выполнения
- Создание и настройка камеры
- Создание и настройка источников света
- Создание, применение и удаление материалов во время выполнения
- Управление существующими объектами сцены
- Выполнение базовых преобразований (перемещение, поворот, масштабирование)
- Как сделать игровой процесс независимым от частоты кадров
- Управление пересечениями
- Получение пользовательских вводов и управление ими
- Создание пользовательского интерфейса
- Воспроизведение звука и музыки
- Настройка физики
- Обнаружение объектов с помощью World Trigger
Основные объекты сцены#
В UNIGINE нода (node) — это базовый тип, от которого наследуются все типы объектов сцены. Некоторые из них отображаются визуально: объекты, декали и эффекты. Объекты имеют поверхности для представления их геометрии (меш). Другие ноды (Источники света, камеры и т.д.) невидимы.
Каждая нода имеет матрицу преобразования, которая кодирует положение, поворот и масштаб ноды в мире.
Все объекты сцены, добавленные в сцену, независимо от их типа, называются нодами.
Дополнительная информация:
- Дополнительные сведения о типах объектов UNIGINE см. в разделе Встроенные типы объектов.
- Для получения дополнительной информации об управлении нодами с помощью API см. раздел Node-классы.
Система координат#
Трехмерное пространство в UNIGINE представлено правой декартовой системой координат: оси X и Y образуют горизонтальную плоскость, ось Z направлена вверх. При экспорте анимации из 3D-редакторов Y считается прямым направлением.
Положительный угол поворота задает вращение против часовой стрелки. Это соответствует правилу правой руки: если вы установите большой палец правой руки вдоль оси, другие обернутые пальцы будут показывать направление вращения.
Дополнительная информация:
- Дополнительные сведения о типах объектов UNIGINE см. в разделе Встроенные типы объектов.
- Для получения дополнительной информации об управлении нодами с помощью API см. раздел Node-классы.
Ведение журнала и вывод сообщений в консоль#
Вывод сообщений в файл журнала и консоль помогает отслеживать общий ход выполнения вашего приложения и сообщать об ошибках, которые могут быть использованы при отладке. Класс Log позволяет печатать форматированные строковые сообщения в файл журнала и консоль. Приведенный ниже код демонстрирует, как выводить различные типы сообщений:
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 (Windows only)
Log::fatal("FATAL ERROR reading \"%s\" file!\n", file_name);
Дополнительная информация:
Сохранение и загрузка мира#
Некоторые приложения управляют одним миром, в то время как другие требуют управления несколькими мирами. В любом случае, очень полезно знать, как сохранить наш текущий мир и загрузить какой-нибудь другой. Чтобы решить эту задачу, мы должны использовать класс World, который разработан как синглтон.
#include <UnigineWorld.h>
using namespace Unigine;
/* .. */
// loading world from the my_world.world file
World::loadWorld("my_world");
Мы также можем сделать то же самое через консоль, используя класс Console, который также разработан как синглтон.
#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.
Чтобы закрыть приложение, необходимо использовать следующий код:
using namespace Unigine;
/* .. */
// closing the application
Engine::get()->quit();
Создание и удаление нод во время выполнения#
Ноды можно создавать и удалять во время выполнения почти так же просто, как в редакторе. Основной набор действий заключается в следующем:
- Создание. Чтобы создать ноду, мы должны объявить интеллектуальный указатель для типа ноды, которую мы собираемся создать, и вызвать конструктор соответствующего класса, при необходимости предоставляя параметры построения.
- Удаление. Чтобы удалить ноду, мы просто вызываем метод deleteLater() для ноды, которую мы собираемся удалить.
// creating a node of the NodeType named nodename
<NodeType>Ptr nodename = <NodeType>::create(<construction_parameters>);
// removing the node
nodename.deleteLater();
Теперь давайте проиллюстрируем процесс создания и удаления ноды на примере простого статического меша (ObjectMeshStatic) с использованием геометрии загружаемой из ассета.
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, который разработан как синглтон.
Следующий код иллюстрирует создание PlayerSpectator и установку его в качестве активной игровой камеры.
#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 создаются так же, как и все остальные ноды.
Давайте рассмотрим создание мирового источника света в качестве примера:
#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, который используется для управления каждым отдельным материалом.
Следующий код можно использовать для создания нового материала, унаследованного от материала mesh_base.
#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->setMaterialFilePath("materials/my_mesh_base0.mat", 0);
// assigning a "my_mesh_base0.mat" material to all surfaces of the my_mesh ObjectMeshDynamic node
my_mesh->setMaterialFilePath("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() — когда мы знаем идентификатор ноды
Эти методы возвращают значение NodePtr, которое является указателем на базовый класс, но для выполнения операций с определенным объектом (например, ObjectMeshDynamicPtr) нам необходимо выполнить понижающее преобразование (т.е. преобразовать указатель на базовый класс в указатель на производный).
Иногда вам также может потребоваться выполнить upcasting (т.е. преобразовать указатель на производный в указатель на базовый), в этом случае вы можете использовать сам производный класс. Приведенный ниже код демонстрирует моменты, описанные выше.
#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;
Существуют следующие способы получения компонента для ноды:
// 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(), которые работают с локальными и мировыми матрицами преобразования соответственно. Следующий код иллюстрирует, как выполнять базовые преобразования нод:
// 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. Этот класс предназначен для использования, когда вы хотите ускорить, замедлить или приостановить рендеринг, физику или игровую логику.
Чтобы изменить преобразования, вы можете использовать следующий код:
#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;
}
Чтобы выполнить некоторые изменения один раз за определенный промежуток времени, вы можете использовать следующий код:
#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;
}
Дополнительная информация:
Управление пересечениями#
Пересечения широко используются в 3D-приложениях. В UNIGINE существует три основных типа пересечений:
- World Intersection — пересечение с объектами и нодами.
- Physics Intersection — пересечение с формами и коллизионными объектами.
- Game Intersection — пересечение с нодами поиска пути, такими как препятствия.
Но есть некоторые условия для обнаружения пересечений с поверхностью:
- Поверхность включена.
- Поверхности присвоен материал.
Для каждой поверхности включен флаг Intersection.
ПримечаниеВы можете установить этот флаг на поверхность объекта с помощью функции Object.setIntersection().
Приведенный ниже код иллюстрирует несколько способов использования мировых пересечений:
- чтобы найти все ноды, пересекаемые ограничивающим прямоугольником;
- чтобы найти все ноды, пересекаемые ограничивающей сферой;
- чтобы найти все ноды, пересекаемые ограничивающим усеченным конусом;
- чтобы найти первый объект, пересеченный лучом.
#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
- Класс Controls
- Класс ControlsApp
Следующий код иллюстрирует, как использовать класс Input для получения координат мыши в случае, если была нажата правая кнопка мыши, и для закрытия приложения, если была нажата клавиша "q" (игнорируя эту клавишу, если консоль открыта):
#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 для обработки ввода с клавиатуры:
#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 для сопоставления клавиш и кнопок с состояниями, а затем для обработки пользовательского ввода:
#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.
Существует 2 способа создания макета графического интерфейса:
- Непосредственно из кода через GUI-классы
- С помощью Файлов пользовательского интерфейса (UI)
Следующий код демонстрирует, как добавить метку и ползунок в системный графический интерфейс:
#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;
}
Чтобы использовать элементы графического интерфейса, мы должны указать обработчики для различных событий (щелчок, изменение и т.д.). Следующий код демонстрирует, как настроить обработчики событий:
#include <UnigineUserInterface.h>
// injecting Unigine namespace to the global namespace
using namespace Unigine;
GuiPtr gui;
// EventConnections class instance to manage event subscriptions
EventConnections econnections;
/// 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(econnections, onButton1Clicked);
// subscbribing for the clicked event with the onButton2Clicked function as a handler
buttonwidget2->getEventClicked().connect(econnections, this, &AppWorldLogic::onButton2Clicked);
buttonwidget1->getEventClicked().connect(econnections, this, &AppWorldLogic::onButtonDelClicked);
// setting AppWorldLogic::onSliderChanged method as a changed event handler for a widget_slider
widget_slider->getEventChanged().connect(econnections, this, &AppWorldLogic::onSliderChanged);
/* .. */
return 1;
}
int AppWorldLogic::shutdown()
{
// removing all event subscriptions somewhere on shutdown
econnections.disconnectAll();
return 1;
}
Дополнительная информация:
- Для получения дополнительной информации о файлах пользовательского интерфейса см. статью Файлы пользовательского интерфейса.
- Для получения дополнительной информации об API, связанном с графическим интерфейсом, см. раздел GUI-классы.
- Для получения дополнительной информации о классе Gui см. статью Gui класс.
- Для получения дополнительной информации об обработке событий см. статью Обработка событий.
Воспроизведение звука и музыки#
Источник звука SoundSource#
Класс SoundSource используется для создания направленных источников звука. Чтобы создать источник звука, создайте экземпляр класса SoundSource и укажите все необходимые настройки:
// 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, укажите все необходимые параметры и включите его. Убедитесь, что вы импортировали звуковой ассет в проект.
// 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.
Настройка физики#
Объект должен иметь тело и форму, чтобы на него воздействовала сила тяжести и он мог сталкиваться с другими физическими объектами:
// 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 обнаруживает только ноды с включенным Triggers Interaction - в Редакторе или при помощи setTriggerInteractionEnabled()
Функция-обработчик события World Trigger фактически выполняется только тогда, когда вызывается следующая функция механизма: то есть перед updatePhysics() (в текущем кадре) или перед update() (в следующем кадре) — независимо от того, что будет первым.
Вы можете подписаться на события Enter и Leave, указав функции-обработчики, которые будут выполняться, когда нода входит в World Trigger или выходит из него. Функция-обработчик должна получать ноду в качестве своего первого аргумента.
// EventConnections class instance to manage event subscriptions
EventConnections econnections;
// 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());
}
// implement the leave event handler
void AppWorldLogic::leave_event_handler(const NodePtr &node)
{
Log::message("\nA node named %s has left 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(econnections, this, &AppWorldLogic::enter_event_handler);
// add the leave event handler to be executed when a node leaves the world trigger
trigger->getEventLeave().connect(econnections, this, &AppWorldLogic::leave_event_handler);
return 1;
}
int AppWorldLogic::shutdown()
{
// removing all event subscriptions somewhere on shutdown
econnections.disconnectAll();
return 1;
}
Информация, представленная на данной странице, актуальна для версии UNIGINE 2.20 SDK.