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.MousePosition;
		// пишем текущие координаты курсора в Консоль
		Log.Message("Right mouse button was clicked at {0}\n", mouse);
	}

	// закрываем приложение при нажатии клавиши 'q' на клавиатуре, игнорируя нажатие, если открыта Консоль
	if (Input.IsKeyDown(Input.KEY.Q) && !Unigine.Console.Active)
	{
		Engine.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#)
void listNodes(List<Node> nodes, string intersection_with)
{
	Log.Message("total number of nodes intersecting a {0} is: {1} \n", intersection_with, nodes.Count);
	foreach (Node node in nodes)
	{
		Log.Message("Intersected node: {0} \n", node.Name);
	}

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

private void Update()
{

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

	// создаем список для хранения обнаруженных нод
	List<Node> nodes = new <Node>();

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

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

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

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

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

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

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

	// инициализируем ограничивающую усеченную пирамиду по фрустуму камеры игрока
	WorldBoundFrustum boundFrustum = new WorldBoundFrustum(player.Camera.Projection, player.Camera.Modelview);

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

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

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

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

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

	// выводим в Консоль имя первого объекта пересеченного лучом и координаты точки пересечения при обнаружении
	if (obj)
	{
		Vec3 p = intersection.Point;
		Log.Message("The first object intersected by the ray at point ({0}) is: {1} \n ", p, obj.Name);
	}
}

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, и напишем в нем следующий код:

Исходный код (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
#else
	using Vec3 = Unigine.vec3;
#endif
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- идентификатор генерируется автоматически для нового компонента
public class InputProcessor : Component
{
	private Player player = Game.Player;
	private Unigine.Object SelectedObject = null;	// последний выбранный объект

	public WorldIntersection intersection = null;	// точка пересечения с последним объектом под курсором мыши
	
	private void Init()
	{
		// создаем экземпляр класса WorldIntersection для хранения информации о пересечении
		intersection = new WorldIntersection();
	}

	private void Update()
	{
		// если консоль открыта, ничего не делаем
		if(Unigine.Console.Active)
		return;

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

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

		// если объект интерактивный (есть компонент Interactable), выводим информацию о нем на экран
		Interactable interactable = obj.GetComponent<Interactable>();
		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'
			SelectedObject.GetComponent<Interactable>().Action(1);
		}

		// проверяем, если нажата клавиша 'Q', то закрываем приложение
		if(Input.IsKeyDown(Input.KEY.Q) && !Unigine.Console.Active)
		{
			Engine.Quit();
		}
	}
}

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

Now let's launch our application by clicking on the Play button and try to turn the switches on and off. 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Теперь запустим наше приложение, нажав на кнопку Play, и попробуем пощелкать выключателями. После запуска приложения, чтобы увидеть на экране подсказки, не забудьте включить Visualizer – просто откройте консоль и введите: show_visualizer 2.

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