This page has been translated automatically.
Основы UNIGINE
1. Введение
2. Виртуальные миры и работа с ними
3. Подготовка 3D моделей
4. Материалы
5. Камеры и освещение
7. Создание кат-сцен и запись видео
8. Подготовка проекта к релизу
9. Физика
10. Основы оптимизации
11. ПРОЕКТ2: Шутер от первого лица
12. ПРОЕКТ3: Аркадная гонка по пересеченной местности от 3-го лица
13. ПРОЕКТ4: VR приложение с простым взаимодействием

Обработка ввода (клавиатура, мышь). Intersections

Now we have the first interactive objects, but no interaction yet.Ну вот теперь у нас есть первые интерактивные объекты, но пока нет взаимодействия.

The good old way of interacting with the user is processing input from the user received from different devices (mouse, keyboard, joystick, etc.). The main class in charge of all this in UNIGINE is Input class.Старый добрый способ взаимодействия с пользователем – обработка ввода от него получаемого с разных устройств (мышь, клавиатура, джойстики и т.п.). Главный ответственный за все это в UNIGINE – это класс Input.

The following code illustrates how to use the Input class to get the cursor coordinates when the right mouse button is pressed, and to close the application when the "q" key on the keyboard is pressed (ignoring this key if the Console is open):Следующий код иллюстрирует, как использовать класс Input для получения координат курсора при нажатии правой кнопки мыши, и для закрытия приложения, при нажатии клавиши "q" на клавиатуре (игнорируя эту клавишу, если открыта Консоль):

Исходный код (C++)
private void update()
{

	// если нажата правая кнопка мыши
	if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
	{
		// берем текущие координаты курсора мыши
		ivec2 mouse = Input::getMousePosition();
		// и пишем координаты в Консоль
		Log::message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
	}

	// закрываем приложение при нажатии клавиши 'q' на клавиатуре, игнорируя нажатие, если открыта Консоль
	if (Input::isKeyDown(Input::KEY_Q) && !Unigine::Console::isActive())
	{
		Engine::get()->quit();
	}
}

Intersections are widely used in 3D applications for a wide range of tasks, such as selecting objects with the mouse, simplified modeling of car wheels, identifying objects and players caught in the blast zone and much more. There are three main types of intersections in UNIGINE:Пересечения (Intersections) широко используются в 3D-приложениях для решения широкого круга задач, это может быть выделение объектов мышкой, упрощенное моделирование колес автомобиля, определение предметов и игроков, попавших в зону поражения взрыва и много чего еще. В UNIGINE существует три основных типа пересечений:

  • World Intersection — intersection with objects and nodes.World Intersection — пересечение с объектами и нодами.
  • Physics Intersection — intersection with shapes and collision objects.Physics Intersection — пересечение с формами и коллизионными объектами.
  • Game Intersection — intersection with pathfinding nodes such as obstacles.Game Intersection — пересечение с нодами поиска пути, такими как препятствия.

However, there are conditions that should be fulfilled to ensure surface intersection detection:Но есть некоторые условия для обнаружения пересечений с поверхностью:

  • The surface must be enabled.Поверхность должна быть включена.
  • The surface must have a material assigned.Поверхность должна иметь материал.
  • The Intersection flag must be enabled for each surface. This can be done via the API using the Object::setIntersection() method.Для каждой поверхности должен быть включен флаг Intersection. Через API это можно сделать при помощи метода Object::setIntersection().

The code below shows several usage examples for intersections:Приведенный ниже код показывает несколько вариантов использования пересечений:

  • Finding all nodes intersected by a bounding box.чтобы найти все ноды, пересекаемые ограничивающим прямоугольником;
  • Finding all nodes intersected by a bounding sphere.чтобы найти все ноды, пересекаемые ограничивающей сферой;
  • Finding all nodes intersected by a bounding frustum.чтобы найти все ноды, пересекаемые ограничивающей усеченной пирамидой (frustum);
  • Finding the first object intersected with a ray (raycast).чтобы найти первый объект, пересеченный лучом (рейкаст).
Исходный код (C++)
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());
	}

	// очищаем список нод
	nodes.clear();

	return 1;
}

int AppWorldLogic::update()
{

	// берем ссылку на текущую камеру (Player)
	PlayerPtr player = Game::getPlayer();

	// создаем список для хранения обнаруженных нод
	Vector<Ptr<Node>> nodes;

	//-------------------------- ПОИСК ПЕРЕСЕЧЕНИЯ С BOUNDING BOX -------------------------

	// задаем ограничивающий объем (Bound Box) размером 3 единицы в начале координат
	WorldBoundBox boundBox(Math::Vec3(0.0f), Math::Vec3(3.0f));

	// ищем пересекаемые баундом ноды и заносим их в список при обнаружении
	if (World::getIntersection(boundBox, nodes))
		listNodes(nodes, "bounding box");

	//------------------------- ПОИСК ПЕРЕСЕЧЕНИЯ С BOUNDING SPHERE ------------------------

	// задаем ограничивающий объем (Bound Sphere) радиусом 3 единицы в начале координат
	WorldBoundSphere boundSphere(Math::Vec3(0.0f), 3.0f);

	// ищем пересекаемые баундом ноды и заносим их в список при обнаружении
	if (World::getIntersection(boundSphere, nodes))
		listNodes(nodes, "bounding sphere");

	//------------------------- ПОИСК ПЕРЕСЕЧЕНИЯ С  BOUNDING FRUSTUM -----------------------

	// инициализируем ограничивающую усеченную пирамиду по фрустуму камеры игрока
	WorldBoundFrustum boundFrustum(player->getCamera()->getProjection(), player->getCamera()->getModelview());

	// ищем ноды ObjectMeshStatic попадающие в нашу усеченную пирамиду и при наличии выводим список
	if (World::getIntersection(boundFrustum, Node::OBJECT_MESH_STATIC, nodes))
		listNodes(nodes, "bounding frustum");

	//---------------- ПОИСК ПЕРВОГО ОБЪЕКТА, ПЕРЕСЕЧЕННОГО ЛУЧОМ ИЗ ТОЧКИ P0 В ТОЧКУ P1 --------------

	// задаем начало отрезка (p0) в позиции камеры и конец (p1) - в точке, направление в которую указывает курсор мыши
	Math::ivec2 mouse = Input::getMousePosition();
	Math::Vec3 p0 = player->getWorldPosition();
	Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100;

	// создаем экземпляр класса WorldIntersection для хранения информации о пересечении
	WorldIntersectionPtr intersection = WorldIntersection::create();

	// пускаем луч из точки p0 в точку p1 чтобы найти первый пересеченный объект
	ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection);

	// выводим в Консоль имя первого объекта пересеченного лучом и координаты точки пересечения при обнаружении
	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;
}

Interacting with objects in the scene using the mouse usually involves detecting the object under the cursor. The last example (raycast) is used for this purpose. We cast a ray from the position of the observer (Player) in the direction of the mouse cursor coordinates and look for the first object intersected by our ray. Взаимодействие с объектами сцены при помощи мыши, как правило, предполагает определение объекта, находящегося под курсором. Для этого как раз и используется последний вариант из примера (рейкаст). Из позиции наблюдателя-игрока (Player) пускаем луч в направлении координат курсора мыши и ищем первый пересекаемый нашим лучом объект.

Practice
Практика#

To test out interactive objects in our application, we desperately need the component implementing this functionality, processing the mouse click, and sending the Action signal (user action) on the selected object to the Interactable component. In addition, the component will handle the keystrokes:В нашем приложении для того, чтобы проверить наши интерактивные объекты, нам очень нужен компонент, реализующий этот функционал, обрабатывающий щелчок мыши и отправляющий компоненту Interactable на выделенном объекте сигнал Action (действие пользователя). Кроме того, компонент будет обрабатывать нажатие клавиш на клавиатуре:

  • Q – Close the application.Q – Закрывать приложение.
  • TAB – Send an additional Action (1) signal to the Interactable component on the selected object (for those components that have it implemented).TAB – Отправлять компоненту Interactable на выделенном объекте сигнал дополнительного действия Action (1) (для тех компонентов, у которых оно реализовано)).

Let's create a new component, name it InputProcessor, and write the following code in it:Создадим новый компонент, назовем его InputProcessor, и напишем в нем следующий код:

InputProcessor.h
#pragma once
#include <UnigineComponentSystem.h>
#include <UnigineGame.h>
#include <UniginePlayers.h>
#include <UnigineWorld.h>
#include <UnigineConsole.h>
#include <UnigineEngine.h>

class InputProcessor :
	public Unigine::ComponentBase
{
public:
	// Объявляем конструктор и деструктор для нашего класса, а также задаем имя связанного с компонентом свойства (property). 
	// Файл InputProcessor.prop со всеми описанными ниже параметрами будет сгенерирован Компонентной системой в папке 'data' вашего проекта при первом запуске приложения
	COMPONENT_DEFINE(InputProcessor, ComponentBase);

	Unigine::WorldIntersectionPtr intersection = nullptr;	// точка пересечения с последним объектом под курсором мыши
	
	// регистрация методов, которые будут вызываться на соответствующих этапах жизненного цикла приложения (сами методы объявляются в protected-блоке ниже)
	COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);

protected:
	// объявление методов, которые будут вызываться на соответствующих этапах жизненного цикла приложения
	void init();
	void update();

private:
	Unigine::PlayerPtr player = Unigine::Game::getPlayer();	// камера
	Unigine::ObjectPtr SelectedObject = nullptr;			// последний выбранный объект
};
InputProcessor.cpp
#include "InputProcessor.h"
#include "Interactable.h"
#include <UnigineVisualizer.h>

// Регистрация компонента InputProcessor
REGISTER_COMPONENT(InputProcessor);

using namespace Unigine;
using namespace Math;

// метод инициализации компонента
void InputProcessor::init()
{
	// создаем экземпляр класса WorldIntersection для хранения информации о пересечении
	intersection = WorldIntersection::create();
}

// метод update компонента, вызываемый каждый кадр 
void InputProcessor::update()
{
	// если консоль открыта, ничего не делаем
	if (Unigine::Console::isActive())
		return;

	// задаем начало отрезка (p0) в позиции камеры и конец (p1) - в точке, направление в которую указывает курсор мыши
	ivec2 mouse = Input::getMousePosition();
	Vec3 p0 = player->getWorldPosition();
	Vec3 p1 = p0 + Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100;

	// пускаем луч из точки p0 в точку p1 чтобы найти первый пересеченный объект
	Unigine::ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection);

	// если объект интерактивный (есть компонент Interactable), выводим информацию о нем на экран
	Interactable* interactable = nullptr;
	interactable = ComponentSystem::get()->getComponentInChildren<Interactable>(obj);
	if (!interactable)
		interactable = ComponentSystem::get()->getComponentInParent<Interactable>(obj);

	if (interactable)
		interactable->displayInfo();

	// проверяем, состояние правой кнопки мыши
	if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
	{
		// если объект интерактивный (есть компонент Interactable)
		if (obj && interactable)
		{
			// посылаем объекту сигнал 'Выполнить действие 0'
			interactable->action(0);
			// запоминаем его как последний выделенный
			SelectedObject = obj;
		}
	}

	// проверяем, если какой-либо объект выделен и нажата клавиша 'TAB' 
	if (SelectedObject && Input::isKeyDown(Input::KEY_TAB))
	{
		// посылаем объекту сигнал 'Выполнить действие 1'
		ComponentSystem::get()->getComponent<Interactable>(SelectedObject)->action(1);
	}

	// проверяем, если нажата клавиша 'Q', то закрываем приложение
	if (Input::isKeyDown(Input::KEY_Q) && !Unigine::Console::isActive())
	{
		Engine::get()->quit();
	}
}

Let's save our files and then build and run our application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign our component to nodes. Close the application after running it and switch to UnigineEditor.Сохраним файлы, а затем соберем и запустим приложение нажав Ctrl + F5, чтобы Компонентная система сгенерировала property для связи компонента с нодой. После запуска приложения закроем его и вернемся в UnigineEditor.

Next, let's create an empty NodeDummy, name it input_processor, and assign our new generated InputProcessor property to it (this is a common practice for components with general purpose functionality).Cоздадим пустую ноду NodeDummy, назовем ее input_processor и назначим на нее новое сгенерированное property InputProcessor (это частая практика для компонентов с функционалом общего назначения).

Save the world by hitting Ctrl + S. Switch to SDK Browser and change the default world for our application in Customize Run Options, by clicking an ellipsis under the Run button on the project's card (type -console_command "world_load archviz" in the Arguments field then check Remember and click Run to launch our application by clicking the Run button.Сохраним мир, нажав Ctrl + S. Переключимся в SDK Browser и поменяем загружаемый по умолчанию мир для нашего приложения в Customize Run Options, нажав многоточие рядом с кнопкой Run на карточке проекта (введем -console_command "world_load archviz" в поле Arguments, выберем Remember и щелкнем Run.

Now let's try turning the switches on and off.A теперь попробуем пощелкать выключателями.

After launching the application, don't forget to enable Visualizer to see the prompts on the screen — just open the console and type: show_visualizer 2После запуска приложения, чтобы увидеть на экране подсказки, не забудьте включить Visualizer – просто откройте консоль и введите: show_visualizer 2.

Последнее обновление: 06.11.2024
Build: ()