This page has been translated automatically.
Видеоуроки
Интерфейс
Основы
Продвинутый уровень
Подсказки и советы
Основы
Программирование на C#
Рендеринг
Профессиональный уровень (SIM)
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Режимы вывода изображения
Физика
Браузер SDK 2
Лицензирование и типы лицензий
Дополнения (Add-Ons)
Демонстрационные проекты
API Samples
Редактор 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
Учебные материалы

Начало работы с виртуальной реальностью

Примечание
The article reviews the creation of C# VR projects only. You can switch to the C++ version in the upper right corner of the page.В статье рассматривается создание VR-проектов только на C#. Вы можете переключиться на версию C++ в правом верхнем углу страницы.

This article is for anyone who wants to start developing Virtual Reality projects in UNIGINE and is highly recommended for all new users. We're going to use the VR Template for C# to easily get started on our own project for VR. We will also look into a VR sample (included in the C# VR template), which can be used as the basis for a VR application, and consider some simple examples of extending the basic functionality of this sample.Эта статья предназначена для тех, кто хочет начать разрабатывать проекты виртуальной реальности в UNIGINE, и настоятельно рекомендуется всем новым пользователям. Мы будем использовать шаблон VR Template для C#, чтобы приступить к работе над нашим собственным проектом для виртуальной реальности. Мы также рассмотрим VRсэмпл (входящий в состав VR шаблона C#), который можно использовать в качестве основы для VR-приложения, и рассмотрим несколько простых примеров расширения базовой функциональности этого примера.

So, let's get started!Итак, давайте начнем!

1. Making a Template Project
1. Создание шаблона проекта#

UNIGINE has a ready-to-use template for development of C# VR projects. It supports all SteamVR-compatible devices out of the box. It also includes a sample called vr_sample the corresponding world with a set of props and components is located in the data/vr_template/vr_sample/ folder (please do not confuse it with the VR Sample Demo project which is available for C++ API only). The template provides automatic loading of controller models in run-time. Additionally, it includes the implementation of basic mechanics such as grabbing and throwing objects, pressing buttons, opening/closing drawers, and a lot more.В UNIGINE есть готовый к использованию шаблон для разработки VR-проектов на C#. Он поддерживает все устройства, совместимые со SteamVR, "из коробки". В него также входит демонстрационный пример vr_sample, соответствующий мир с набором предметов и компонент располагается в папке data/vr_template/vr_sample/ (пожалуйста не путайте этот сэмпл с демонстрационным проектом VR Sample, который доступен долько для C++ API). Шаблон обеспечивает автоматическую загрузку моделей контроллеров во время выполнения. Кроме того, он включает в себя реализацию базовых механик, таких как захват и бросание предметов, нажатие кнопок, открытие/закрывание ящиков и многое другое.

Примечание
The world in this sample project has its settings optimized for the best performance in VR.Настройки мира в этом шаблоне проекта оптимизированы для достижения наилучшей производительности в виртуальной реальности.

Both the template and the sample project are created using the Component System - components attached to objects determine their functionality.И шаблон, и демо-проект создаются с использованием Компонентной системы — компоненты, прикрепленные к объектам, определяют их функциональность.

Примечание
A brief overview of the template structure and a description of its main components are available through the link.Краткий обзор структуры шаблона и описание его основных компонентов доступны по ссылке.

You can extend object's functionality simply by assigning it more components. For example, the laser pointer object in the vr_sample has the following components attached:Вы можете расширить функциональность объекта, просто назначив на него больше компонентов. Например, на объект "лазерная указка" в vr_sample назначены следующие компоненты:

You can also implement your own components to extend functionality.Вы также можете реализовать свои собственные компоненты для расширения функциональности.

The main features of the embedded vr_sample:Основные характеристики встроенного сэмпла vr_sample:

  • VR supportПоддержка виртуальной реальности
  • A set of physical objects that can be manipulated via controllers (physical objects, buttons, drawers)Набор физических объектов, которыми можно управлять с помощью контроллеров (физические объекты, кнопки, выдвижные ящики)
  • An interactive laser pointer objectИнтерактивный объект — лазерная указка
  • Teleportation around the sceneТелепортация по сцене
  • GUIГрафический интерфейс пользователя
  • Interaction with GUI objects via controllersВзаимодействие с объектами графического интерфейса пользователя через контроллеры

Let's create a project based on the C# VR Template.Давайте создадим проект на основе C#-шаблона VR Template.

  1. Open SDK Browser, go to the My Projects tab, and click Create New.Откройте SDK Browser, перейдите на вкладку My Projects и нажмите Create New.
  2. In the Create New Project window, specify the project name VRProject, select VR in the Template menu, then C#(.NET) in the API+IDE menu, and select Float in the Precision menu. Then click Create New Project.В окне Create New Project укажите название проекта VRProject, выберите VR в меню Template, затем C#(.NET) в меню API+IDE и выберите Float в меню Precision. Затем нажмите Create New Project.

  3. When the project is successfully created, open it in UnigineEditor.Когда проект будет успешно создан, откройте его в UnigineEditor.
  4. In the Asset Browser, open the vr_sample.world file located in the data/vr_template/vr_sample/ folder. We will work with this world.В браузере ассетов откройте файл vr_sample.world, расположенный в папке data/vr_template/vr_sample/. Мы будем работать с этим миром.

2. Setting Up a Device and Configuring Project
2. Подключение устройства и настройка проекта#

Suppose you have successfully installed your Head-Mounted Display (HMD) of choice.Предположим, вы установили нужный вам VR-шлем (HMD).

Learn more on setting up devices for diffent VR platforms. If you have any difficulties, please visit the Steam Support. In case of Vive devices, this Troubleshooting guide might be helpful.Более подробную информацию о настройке устройств для различных платформ VR вы можете найти по этой ссылке. Если у вас возникли трудности, обратитесь в службу поддержки Steam. В случае с устройствами Vive может быть полезно это руководство по устранению неполадок.

Примечание
For Mixed Reality application development, you will need to download and install Varjo Base in addition to SteamVR.Для разработки приложения Mixed Reality вам потребуется загрузить и установить Varjo Base в дополнение к SteamVR.

All SteamVR-compatible devices are supported out-of-the-box.Все устройства, совместимые со SteamVR, поддерживаются «из коробки».

By default, VR is not initialized. So, you need to perform one of the following:По умолчанию VR не инициализирован. Поэтому вам необходимо выполнить одно из следующих действий:

  • If you run the application via UNIGINE SDK Browser, set the Stereo 3D option to the value that corresponds to the installed HMD (OpenVR or Varjo) in the Global Options tab and click Apply.Если вы запускаете приложение через UNIGINE SDK Browser, установите для параметра Stereo 3D значение, соответствующее установленному HMD (OpenVR или Varjo) на вкладке Global Options и нажмите Apply.

    Alternatively, you can specify the -vr_app command-line option in the Customize Run Options window, when running the application via the SDK Browser.В качестве альтернативы вы можете указать параметр командной строки -vr_app в окне Customize Run Options при запуске приложения через браузер SDK.

  • If you launch the application from UnigineEditor, specify the corresponding VR mode:Если вы запускаете приложение из UnigineEditor, укажите соответствующий режим VR mode:

Примечание
The integration of OpenVR functionality in the UNIGINE VR system enables the implementation of applications for Oculus HMDs. That's why the OpenVR should be specified in case of utilizing Oculus devices.Интеграция функционала OpenVR в систему виртуальной реальности UNIGINE позволяет создавать приложения для устройств Oculus HMD. Вот почему при использовании устройств Oculus следует указывать значение OpenVR.

Run the application and learn about basic mechanics, grab and throw objects, try to teleport and interact with objects in the scene.Запустите приложение и изучите основные механики, берите и бросайте предметы, попробуйте телепортироваться и взаимодействовать с объектами в сцене.

3. Extending Functionality
3. Расширение функциональных возможностей#

So, as a part of our first VR project, let's learn how to use the vr_sample functionality and how it can be extended.Итак, в рамках нашего первого VR-проекта давайте узнаем, как использовать функциональность vr_sample и как ее можно расширить.

3.1. Attaching Object to HMD
3.1. Прикрепление объектов к HMD#

Sometimes it might be necessary to attach some object to the HMD to follow it. It can be a menu (like the menu_attached_to_head node in the sample), or glasses with colored lenses, or a smoking cigar. To attach a node to the HMD position, just assign the VRAttachToHead component to this node and specify the required settings: distance, forward direction axis, and flags indicating whether the node position is fixed and updated when the component is enabled.Иногда может потребоваться прикрепить к HMD какой-либо объект, чтобы он следовал за камерой. Это может быть меню (например, нода menu_attached_to_head в примере), очки с цветными линзами или дымящаяся сигара. Чтобы привязать ноду к позиции HMD, просто назначьте этой ноде компонент VRAttachToHead и укажите необходимые настройки: расстояние, ось направления вперед и флаги, указывающие, является ли положение ноды зафиксированным и обновляется ли оно при включении компонента.

3.2. Attaching Objects to Controllers
3.2. Прикрепление объектов к контроллерам#

If you need to attach some object loaded in run-time to a controller (e.g., a menu), you can assign the VRAttachToHand component to this object.Если вам нужно прикрепить к контроллеру какой-либо объект, загруженный во время выполнения (например, меню), вы можете назначить этому объекту компонент VRAttachToHand.

For example, if you have a GUI object and want to attach it to the controller, select this object in the World Hierarchy, click Add New Component or Property in the Parameters window, and specify the VRAttachToHand component.Например, если у вас есть объект GUI и вы хотите подключить его к контроллеру, выберите этот объект в окне World Hierarchy, нажмите Add New Component or Property в окне Parameters и укажите компонент VRAttachToHand.

The component settings allow specifying the controller to which the object should be attached (either left or right), as well as the object's transformation.Настройки компонента позволяют указать контроллер, к которому должен быть прикреплен объект (левый или правый), а также способ преобразования объекта.

In the vr_sample, this component is attached to the menu_attached_to_hand node and initializes widgets in run-time.В vr_sample этот компонент назначен на ноду menu_attached_to_hand и инициализирует виджеты во время выполнения.

3.3. Restricting Teleportations
3.3. Ограничение телепортации#

In VR projects, sometimes we use teleporting to move around, especially in large scenes. By default, it is possible to teleport to any point of the scene. But sometimes, it might be necessary to restrict teleportation to certain areas (for example, to the roof). VR Template provides the TeleportationMovement component (assigned to the vr_layer -> vr_movement_manager -> teleportation_movement node) that implements teleportation logic and allows specifying the maximum teleportation distance, the appearance of the ray and target point, and so on.В проектах виртуальной реальности иногда мы используем телепортацию для перемещения, особенно в больших сценах. По умолчанию можно телепортироваться в любую точку сцены. Но иногда может возникнуть необходимость запретить телепортацию в определенные области (например, на крышу). Шаблон VR Template содержит компонент TeleportationMovement (назначенный на ноду vr_layer -> vr_movement_manager -> teleportation_movement), который реализует логику телепортации и позволяет указать максимальное расстояние телепортации, внешний вид луча и целевой точки и многое другое.

You can teleport to an object if its Physics Intersection mask matches the Teleportation mask in the component settings. By default, the 2nd bit of the Teleportation mask is used. So, you can teleport to objects, for which the Physics Intersection option is enabled and the 2nd bit of the Physics Intersection mask is set.Вы можете телепортироваться к объекту, если его Physics Intersection mask соответствует Teleportation mask в настройках компонента. По умолчанию используется 2-й бит Teleportation mask. Таким образом, вы можете телепортироваться к объектам, для которых включена опция Physics Intersection и включен 2-й бит для параметра Physics Intersection mask.

To restrict teleportation to a certain area, do the following:Чтобы ограничить телепортацию определенной областью, выполните следующие действия:

  1. Create a new Static Mesh object by using the default plane (core/meshes/plane.mesh) or an arbitrary mesh that will define the restricted area.Создайте новый объект Static Mesh, используя плоскость по умолчанию (core/meshes/plane.mesh) или произвольный меш, который будет определять ограниченную область.
  2. Place the Static Mesh in the scene just above the floor.Поместите Static Mesh в сцене чуть выше уровня пола.
  3. In the Parameters window, enable the Physics Intersection option for the object and check that the 2nd bit of the Physics Intersection Mask is disabled.В окне Parameters включите опцию Physics Intersection для объекта и убедитесь, что 2-й бит Physics Intersection Mask отключен.

  4. Save and run the application. Now you cannot teleport to the area defined by the rectangular plane (the ray and the target point are red).Сохраните и запустите приложение. Теперь вы не можете телепортироваться в область, ограниченную прямоугольной плоскостью (луч и целевая точка представлены красным цветом).

  5. To hide the Static Mesh, disable all bits of its Viewport and Shadow masks.Чтобы скрыть Static Mesh, отключите все биты его масок Viewport и Shadow.

  6. Save changes and run the application: the rectangular plane is invisible now.Сохраните изменения и запустите приложение: прямоугольная плоскость теперь невидима.

3.4. Changing Controller Grip Button to Trigger
3.4. Смена действия кнопки захвата контроллера на триггер#

You might want to remap actions or swap the controls in your VR application. As an example, let's do this for the controller. We will remap the Grab action from the Grip button to Trigger, and the Use action to the Grip button.Возможно, вам захочется переназначить действия или поменять местами элементы управления в вашем приложении виртуальной реальности. В качестве примера давайте сделаем это для контроллера. Мы переназначим действие Grab с кнопки Grip на Trigger, а действие Use на кнопку Grip.

Примечание
You can discover how inputs on different types of controllers are mapped to the buttons and axes within the UNIGINE input system.Вы можете ознакомиться с тем, как действия ввода на различных типах контроллеров сопоставляются с кнопками и осями в системе ввода UNIGINE.

User input for the controller is defined in the VRControllerInput component. It is assigned to the left_preset_0 and right_preset_0 nodes (vr_player -> vr_input) in our VR sample.Пользовательский ввод для контроллера определен в компоненте VRControllerInput. Он назначен на ноды left_preset_0 и right_preset_0 (vr_player -> vr_input) в нашем примере виртуальной реальности.

To remap actions, do the following:Чтобы переназначить действия, сделайте следующее:

  1. Select the left_preset_0 node that contains settings for the left controller (the Bind -> Side parameter value is set to LEFT).Выберите ноду left_preset_0, содержащую настройки для левого контроллера (значение параметра Bind -> Side равно LEFT).
  2. Change the Grab Button -> Button value to LEFT_AXIS1, and the Use Button -> Button value to LEFT_GRIP:Измените значение Grab Button -> Button на LEFT_AXIS1, а значение Use Button -> Button на LEFT_GRIP:

  3. Select the right_preset_0 node that contains settings for the right controller.Выберите ноду right_preset_0, содержащую настройки для нужного контроллера.
  4. Change the Grab Button -> Button value to RIGHT_AXIS1, and the Use Button -> Button value to RIGHT_GRIP.Измените значение Grab Button -> Button на RIGHT_AXIS1, а значение Use Button -> Button на RIGHT_GRIP.
  5. Save changes (Ctrl+S) and press the Play button to run the application.Сохраните изменения (Ctrl+S) и нажмите кнопку Play для запуска приложения.

Now you can grab objects by the trigger, and use them by the Grip side button.Теперь вы можете брать объекты при помощи триггера и использовать их с помощью боковой кнопки Grip.

3.5. Adding an Interactive Area
3.5. Добавление интерактивной области#

In games and VR applications, some actions are performed when you enter a special area in the scene (for example, music starts, sound or visual effects appear). Let's implement this in our project: when you enter or teleport to a certain area, it rains inside it.В играх и приложениях виртуальной реальности некоторые действия выполняются, когда вы входите в специальную область сцены (например, начинается музыка, появляются звуковые или визуальные эффекты). Давайте реализуем это в нашем проекте: когда вы входите или телепортируетесь в определенную область, внутри нее идет дождь.

In UNIGINE, the World Trigger node is used to track when any node (collider or not) gets inside or outside of it (for objects with physical bodies, the Physical Trigger node is used).В UNIGINE нода World Trigger используется для отслеживания того, когда какая-либо нода (коллайдер или нет) попадает внутрь или за ее пределы (для объектов с физическими телами используется нода Physical Trigger).

  1. First of all, create a trigger to define the rain area. On the Menu bar, choose Create -> Logic -> World Trigger and place the trigger near the Material Ball, for example. Set the trigger size to (2, 2, 2).Прежде всего, создайте триггер для определения области дождя. В строке меню выберите Create -> Logic -> World Trigger и поместите триггер, например, рядом с Material Ball. Установите размер триггера равным (2, 2, 2).

  2. Create a new TriggerZone component that will turn the rain node on and off (in our case, it is a particle system simulating rain), depending on the trigger state. Add the following code to the component and then assign it to the World Trigger node:Создайте новый компонент TriggerZone, который будет включать и выключать ноду rain (в нашем случае это система частиц, имитирующая дождь) в зависимости от состояния триггера. Добавьте следующий код к компоненту, а затем назначьте его на ноду World Trigger:

    TriggerZone.cs

    Исходный код (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component
    public class TriggerZone : Component
    {
    	private WorldTrigger trigger = null;
    	public Node ControlledNode = null;
    	private void Init()
    	{
    		// check if the component is assigned to WorldTrigger
    		if (node.Type != Node.TYPE.WORLD_TRIGGER)
    		{
    			Log.Error($"{nameof(TriggerZone)} error: this component can only be assigned to a WorldTrigger.\n");
    			Enabled = false;
    			return;
    		}
    		// check if ControlledNode is specified
    		if (ControlledNode == null)
    		{
    			Log.Error($"{nameof(TriggerZone)} error: 'ControlledNode' is not set.\n");
    			Enabled = false;
    			return;
    		}
    		// add event handlers to be fired when a node gets inside and outside the trigger
    		trigger = node as WorldTrigger;
    		trigger.EventEnter.Connect(trigger_enter);
    		trigger.EventLeave.Connect(trigger_leave);
    	}
    	
    	private void Update()
    	{
    		// write here code to be called before updating each render frame
    		
    	}
    
    	// handler function that enables ControlledNode when vr_player enters the trigger
    	private void trigger_enter(Node node)
    	{
    		ControlledNode.Enabled = true;
    	}
    	// handler function that disables ControlledNode when vr_player leaves the trigger
    	private void trigger_leave(Node node)
    	{
    		if (node.Name != "vr_player")
    			return;
    		ControlledNode.Enabled = false;
    	}
    }
  3. Now let's add the rain effect. Drag the rain_effect.node asset (available in the vr/particles folder of the UNIGINE Starter Course Project add-on) to the scene, add it as the child to the trigger node and turn it off (the component will turn it on later).Теперь давайте добавим эффект дождя. Перетащите в сцену компонент rain_effect.node (доступный в папке vr/particles дополнения UNIGINE Starter Course Project), добавьте его в качестве дочернего элемента к ноде триггера и отключите его (компонент включит его позже).

  4. Drag the rain_effect node from the World Hierarchy window to the Controlled Node field of the TriggerZone (in the Parameters window).Перетащите ноду rain_effect из окна World Hierarchy в поле Controlled Node компонента TriggerZone (в окне Parameters).

  5. Select the vr_player (PlayerDummy) node in the World Hierarchy and check the Triggers Interaction option for it to make the trigger react on contacting it.Выберите ноду vr_player (PlayerDummy) в World Hierarchy и установите флаг Triggers Interaction, чтобы триггер реагировал на обращение к ней.

  6. Save changes (Ctrl+S), click Play to run the application, and try to enter the area defined by the trigger - it is raining inside.Сохраните изменения (Ctrl+S), нажмите Play для запуска приложения, и попробуйте войти в область, определенную триггером - внутри идет дождь.

3.6. Adding a New Interaction
3.6. Добавление нового типа взаимодействия#

Another way to extend functionality is to add a new action for the object.Еще один способ расширить функциональность - добавить новое действие для объекта.

We have a laser pointer that we can grab, throw, and use (turn on a laser beam). Now, let's add an alternative use action - change the pointed object's material. This action will be executed when we press and hold the Grip side button on one controller while using the laser pointer gripped by the opposite controller.У нас есть лазерная указка, которую мы можем взять , бросить и использовать (включить лазерный луч). Теперь давайте добавим альтернативное действие - изменим материал объекта, на который направлена указка. Это действие будет выполняться, когда мы нажмем и будем удерживать боковую кнопку захвата на одном контроллере, одновременно используя лазерную указку, удерживаемую противоположным контроллером.

The VRLaserPointer.cs component implements the base functionality of the laser pointer.Компонент VRLaserPointer.cs реализует базовую функциональность лазерной указки.

The base component for all interactive objects is VRBaseInteractable (vr_template/components/base/VRInteractable.cs). It defines all available states of the interactive objects (NOT_INTERACT, HOVERED, GRABBED, USED) and methods executed on certain actions performed on the objects (OnHoverBegin, OnHoverEnd, OnGrabBegin, OnGrabEnd, OnUseBegin, OnUseEnd).Базовым компонентом для всех интерактивных объектов является VRBaseInteractable (vr_template/components/base/VRInteractable.cs). Он определяет все доступные состояния интерактивных объектов (NOT_INTERACT, HOVERED, GRABBED, USED) и методы, выполняемые при определенных действиях, совершаемых с объектами (OnHoverBegin, OnHoverEnd, OnGrabBegin, OnGrabEnd, OnUseBegin, OnUseEnd).

Components inherited from VRBaseInteractable must implement overrides of these methods (for example, the grab logic for the object must be implemented in the OnGrabBegin method of the inherited component).Компоненты, унаследованные от VRBaseInteractable, должны реализовывать переопределения этих методов (например, логика захвата объекта должна быть реализована в методе OnGrabBegin унаследованного компонента).

So, we need to add a new action and map it to a new state (the Grip side button is pressed on one controller while the laser pointer gripped (Grip) by the opposite controller is used):Итак, нам нужно добавить новое действие и преобразовать его в новое состояние (боковая кнопка Grip нажата на одном контроллере, в то время как используется лазерная указка, удерживаемая (Grip) противоположным контроллером):

  1. Add a new OnAltUse action (alternative use) and a new ALT_USED state to the VRBaseInteractable base component:Добавьте новое действие OnAltUse (альтернативное использование) и новое состояние ALT_USED к базовому компоненту VRBaseInteractable:

    VRBaseInteractable.cs

    Исходный код (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component
    public class VRBaseInteractable : Component
    {
    	static public event Action onInit;
    
    	public enum INTERACTABLE_STATE
    	{
    		NOT_INTERACT,
    		HOVERED,
    		GRABBED,
    		USED,
    		ALT_USED   // add a new state
    	}
    
    	public INTERACTABLE_STATE CurrentState { get; set; }
    
    	protected void Init()
    	{
    		onInit?.Invoke(this);
    	}
    
    	public virtual void OnHoverBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnHoverEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    	// add methods for a new "alternative use" action
    	public virtual void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    }
  2. Add overrides of the OnAltUse() and OnAltUseEnd() methods to the VRLaserPointer component (as we are going to add a new action for the laser pointer). In the Update() method, implement the logic of the AltUse action that is executed when the corresponding flags are set.Добавьте переопределения методов OnAltUse() и OnAltUseEnd() к компоненту VRLaserPointer (поскольку мы собираемся добавить новое действие для лазерной указки). В методе Update() реализуйте логику действия AltUse, которое выполняется при установке соответствующих флагов.

    VRLaserPointer.cs

    Исходный код (C#)
    #region Math Variables
    #if UNIGINE_DOUBLE
    	using Scalar = System.Double;
    	using Vec2 = Unigine.dvec2;
    	using Vec3 = Unigine.dvec3;
    	using Vec4 = Unigine.dvec4;
    	using Mat4 = Unigine.dmat4;
    #else
    using Scalar = System.Single;
    using Vec2 = Unigine.vec2;
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
    using WorldBoundBox = Unigine.BoundBox;
    using WorldBoundSphere = Unigine.BoundSphere;
    using WorldBoundFrustum = Unigine.BoundFrustum;
    #endif
    #endregion
    
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component
    public class VRLaserPointer : VRBaseInteractable
    {
    	[ShowInEditor]
    	[Parameter(Title = "Laser", Group = "VR Laser Pointer")]
    	private Node laser = null;
    	
    	[ShowInEditor]
    	[Parameter(Title = "Laser Ray", Group = "VR Laser Pointer")]
    	private Node laserRay = null;
    
    	[ShowInEditor]
    	[Parameter(Title = "Laser Hit", Group = "VR Laser Pointer")]
    	private Node laserHit = null;
    
    	[ShowInEditor]
    	[Parameter(Title = "Object Text", Group = "VR Laser Pointer")]
    	private ObjectText objText = null;
    
    
    	private Mat4 laserRayMat;
    	private WorldIntersection intersection = new WorldIntersection();
    	private float rayOffset = 0.05f;
    
    	private bool grabbed = false;
    
    	private bool altuse = false;
    	public  List<Material> materials = null;
    	private int current_material = 0;
    	
    	protected override void OnReady()
    	{
    	laserRayMat = new Mat4(laserRay.Transform);
    	laser.Enabled = false;
    	// if the list of textures is empty/not defined, report an error
    	if(materials == null || materials.Count < 0)
    	{
    		Log.Error($"{nameof(VRLaserPointer)} error: materials list is empty.\n");
    		Enabled = false;
    		return;
    	}
    	}
    
    	private void Update()
    	{
    		if(laser.Enabled && grabbed)
    		{
    			laserRay.Transform = laserRayMat;
    
    			vec3 dir = laserRay.GetWorldDirection(MathLib.AXIS.Y);
    			Vec3 p0 = laserRay.WorldPosition + dir * rayOffset;
    			Vec3 p1 = p0 + dir * 1000;
    			Unigine.Object hitObj = World.GetIntersection(p0, p1, 1, intersection);
    			if(hitObj != null)
    			{
    				laserRay.Scale = new vec3(laserRay.Scale.x, 
    									MathLib.Length(intersection.Point - p0) + rayOffset, 
    									laserRay.Scale.z);
    				laserHit.WorldPosition = intersection.Point;
    				laserHit.Enabled = true;
    			}
    			else
    			{
    				laserHit.Enabled = false;
    			}
    
    			if (hitObj != null)
    			{
    				objText.Enabled = true;
    				objText.Text = hitObj.Name;
    				float radius = objText.BoundSphere.Radius;
    				vec3 shift = vec3.UP * radius;
    				objText.WorldTransform = MathLib.SetTo(laserHit.WorldPosition + shift, 
    										VRPlayer.LastPlayer.HeadController.WorldPosition, 
    										vec3.UP, 
    										MathLib.AXIS.Z);
    
    				// implement logic of the "alternative use" action (cyclic change of materials on the object according to the list)
    				if(altuse)
    				{
    					current_material++;
    					if (current_material >= materials.Capacity)
    						current_material = 0;
    
    						hitObj.SetMaterial(materials[current_material], 0);
    					Log.Message("ALTUSE HIT\n");
    					altuse = false;
    				}
    			}
    			else
    			objText.Enabled = false;
    		}
    	}
    
    	public override void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		grabbed = true;
    	}
    
    	public override void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		grabbed = false;
    		laser.Enabled = false;
    		objText.Enabled = false;
    	}
    
    	public override void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		if(grabbed)
    			laser.Enabled = true;
    	}
    
    	public override void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		laser.Enabled = false;
    		objText.Enabled = false;
    	}
    
    	// override the method that is called when the "alternative use" action is performed on the object
    	public override void OnAltUse(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		altuse = true;
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.ALT_USED;
    	}
    
    	// override the method that is called when the "alternative use" action is done
    	public override void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		altuse = false;
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.NOT_INTERACT;
    	}
    }
  3. Add an execution condition for the OnAltUse action. All available types of interactions are implemented in the VRHandShapeInteraction component (vr_template/components/interactions/interactions/VRHandShapeInteraction.cs). So, add the following code to the Interact() method of this component:Добавьте условие выполнения для действия OnAltUse. Все доступные типы взаимодействий реализованы в компоненте VRHandShapeInteraction (vr_template/components/interactions/interactions/VRHandShapeInteraction.cs). Итак, добавьте следующий код в метод Interact() этого компонента:

    VRHandShapeInteraction.cs

    Исходный код (C#)
    public override void Interact(VRInteractionManager.InteractablesState interactablesState, float ifps)
    {
    	// ...
    
    	// update current input
    	bool grabDown = false;
    	bool grabUp = false;
    	bool useDown = false;
    	bool useUp = false;
    	// reset flags before checking the current input
    	bool altUse = false;
    	bool altUseUp = false;
    
    
    	switch (controller.Device)
    	{
    		case VRInput.VRDevice.LEFT_CONTROLLER:
    			grabDown = VRInput.IsLeftButtonDown(VRInput.ControllerButtons.GRAB_BUTTON);
    			grabUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.GRAB_BUTTON);
    			useDown = VRInput.IsLeftButtonDown(VRInput.ControllerButtons.USE_BUTTON);
    			useUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.USE_BUTTON);
    			// for the left controller, the Use button must be hold on the right controller
    			altUse = VRInput.IsRightButtonPress(VRInput.ControllerButtons.USE_BUTTON);  
    			altUseUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.USE_BUTTON);
    			break;
    
    		case VRInput.VRDevice.RIGHT_CONTROLLER:
    			grabDown = VRInput.IsRightButtonDown(VRInput.ControllerButtons.GRAB_BUTTON);
    			grabUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.GRAB_BUTTON);
    			useDown = VRInput.IsRightButtonDown(VRInput.ControllerButtons.USE_BUTTON);
    			useUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.USE_BUTTON);
    			// for the right controller, the Use button must be hold on the left controller
    			altUse = VRInput.IsLeftButtonPress(VRInput.ControllerButtons.USE_BUTTON);
    			altUseUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.USE_BUTTON);
    			break;
    
    		case VRInput.VRDevice.PC_HAND:
    			grabDown = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.FIRE_1);
    			grabUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.FIRE_1);
    			useDown = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.FIRE_2);
    			useUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.FIRE_2);
    			// for the keyboard, the JUMP button must be held
    			altUse = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.JUMP);
    			altUseUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.JUMP);
    			break;
    
    		default: break;
    	}
    
    	// stop the "alternative use" action - call OnAltUseEnd
    	// for all components of the hovered object that have an implementation of this method
    	if (altUseUp)
    	{
    		foreach (var hoveredObjectComponent in hoveredObjectComponents)
    			hoveredObjectComponent.OnAltUseEnd(this, controller);
    	}
    
    	// can grab and use hovered object
    	if (hoveredObject != null)
    	{
    		if (grabDown && grabbedObject == null)
    		{
    			grabbedObject = hoveredObject;
    			grabbedObjectComponents.Clear();
    			grabbedObjectComponents.AddRange(hoveredObjectComponents);
    			foreach (var grabbedObjectComponent in grabbedObjectComponents)
    			{
    			if (VRInteractionManager.IsGrabbed(grabbedObjectComponent))
    			{
    				VRBaseInteraction inter = VRInteractionManager.GetGrabInteraction(grabbedObjectComponent);
    				VRInteractionManager.StopGrab(inter);
    			}
    
    			grabbedObjectComponent.OnGrabBegin(this, controller);
    			interactablesState.SetGrabbed(grabbedObjectComponent, true, this);
    			}
    		}
    
    		if (useDown)
    		{
    			usedObject = hoveredObject;
    			usedObjectComponents.Clear();
    			usedObjectComponents.AddRange(hoveredObjectComponents);
    			foreach (var usedObjectComponent in usedObjectComponents)
    			{
    				usedObjectComponent.OnUseBegin(this, controller);
    				interactablesState.SetUsed(usedObjectComponent, true, this);
    			}
    		}
    		// call OnAltUse for all components of the hovered object that have an implementation of this method
    		if (altUse)
    		{
    		foreach (var hoveredObjectComponent in hoveredObjectComponents)
    			if (hoveredObjectComponent.CurrentState != VRBaseInteractable.INTERACTABLE_STATE.ALT_USED)
    				hoveredObjectComponent.OnAltUse(this, controller);
    		altUse = false;
    		}
    	}
    
    	// ...
    }
  4. Specify the list of materials that will be applied to the object. Select the laser_pointer node and click Edit in the Parameters window. Then find the VRLaserPointer component, specify the number of elements for the Materials array (for example, 3) and drag the required materials from the Materials window to fill the array.Укажите список материалов, которые будут применены к объекту. Выберите ноду laser_pointer и нажмите Edit в окне Parameters. Затем найдите компонент VRLaserPointer, укажите количество элементов для массива Materials (например, 3) и перетащите необходимые материалы из окна Materials, чтобы заполнить массив.

  5. Save changes (Ctrl+S) and press the Play button to run the application.Сохраните изменения (Ctrl+S) и нажмите кнопку Play для запуска приложения.

3.7. Customizing Menu
3.7. Кастомизация меню#

The VR template includes two menus: the first one is attached to the HMD position, and the second - to the hand. In the VR sample, the menu_attached_to_head node (ObjectGui) is used for the main menu, and the menu_attached_to_hand node (ObjectGui) - for the additional menu. Шаблон VR включает в себя два меню: первое привязано к позиции HMD, а второе - к руке. В примере VR нода menu_attached_to_head (ObjectGui) используется для главного меню, а нода menu_attached_to_hand (ObjectGui) - для дополнительного меню.

All menu nodes have the same VRMenuSample.cs component assigned. It is inherited from the base VRBaseUI component that implements a user interface. In general, the user interface is implemented as follows:На все ноды меню назначен один и тот же компонент VRMenuSample.cs. Он унаследован от базового компонента VRBaseUI, который реализует пользовательский интерфейс. В целом, пользовательский интерфейс реализован следующим образом:

  1. Inherit a new component from the base VRBaseUI.Из базового компонента VRBaseUI наследуется новый компонент.
  2. Implement the user interface with the required widgets in the overridden InitGui() method.В переопределенном методе InitGui() реализуется пользовательский интерфейс с необходимыми виджетами.
  3. Implement handlers for processing widgets events (pressing a button, selecting elements in the drop-down menu, etc.).Реализуются обработчики для обработки событий виджетов (нажатие кнопки, выбор элементов в выпадающем меню и т.д.).
  4. Subscribe the corresponding widgets to the events in the InitGui() method.Соответствующие виджеты подписываются на события в методе InitGui().
  5. Assign the component to the ObjectGui node that will display the user interface.Компонент назначается на ноду ObjectGui, которая будет отображать пользовательский интерфейс.

Let's create a new menu for the left controller and add the quit button:Давайте создадим новое меню для левого контроллера и добавим кнопку выхода:

  1. Inherit a new component from the base VRBaseUI, call it VRHandMenu and copy the following code to it:Унаследуйте новый компонент от базового VRBaseUI, назовите его VRHandMenu и скопируйте в него следующий код:

    VRHandMenu.cs

    Исходный код (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component
    public class VRHandMenu : VRBaseUI
    {
    	// declare required widgets
    	private WidgetSprite background;
    	private WidgetVBox VBox;
    	private WidgetButton quitButton;
    	private WidgetWindow window;
    	private WidgetButton okButton;
    	private WidgetButton cancelButton;
    	private WidgetHBox HBox;
    
    	// overriden method that is called on interface initialization,
    	// it adds the required widgets and subscribes to events
    	protected override void InitGui()
    	{
    		if (gui == null)
    		return;
    
    		background= new WidgetSprite(gui, "core/textures/common/black.texture");
    		background.Color = new vec4(1.0f, 1.0f, 1.0f, 0.5f);
    		gui.AddChild(background, Gui.ALIGN_BACKGROUND | Gui.ALIGN_EXPAND);
    		
    		// add a vertical column container WidgetVBox to GUI
    		VBox = new WidgetVBox();
    		VBox.Width = gui.Width;
    		VBox.Height = 100;
    		gui.AddChild(VBox, Gui.ALIGN_OVERLAP|Gui.ALIGN_CENTER);
    
    		// add the quit button to the container
    		quitButton = new WidgetButton(gui, "QUIT APPLICATION");
    		quitButton.FontSize = 20;
    		VBox.AddChild(quitButton);
    		quitButton.EventClicked.Connect(ButtonQuitClicked);
    		VBox.Arrange();
    
    		// create a quit confirmation window
    		window = new WidgetWindow(gui, "Quit Application");
    		window.FontSize = 20;
    		
    		// add a horizontal row container WidgetHBox to the confirmation window
    		HBox = new WidgetHBox();
    		HBox.Width = window.Width;
    		HBox.Height = 100;
    		window.AddChild(HBox);
    
    		// add the "OK" button and assign the "OkClicked" handler that will close the application
    		okButton = new WidgetButton(gui, "OK");
    		okButton.FontSize = 20;
    		HBox.AddChild(okButton);
    		okButton.EventClicked.Connect(OkClicked);
    
    		// add the "Cancel" button and assign the "OkClicked" handler that will close the confirmation window
    		cancelButton = new WidgetButton(gui, "Cancel");
    		cancelButton.FontSize = 20;
    		HBox.AddChild(cancelButton);
    		cancelButton.EventClicked.Connect(CancelClicked);
    	}
    
    	// function to be executed on clicking "quitButton"
    	private void ButtonQuitClicked()
    	{
    		gui.AddChild(window, Gui.ALIGN_OVERLAP | Gui.ALIGN_CENTER);
    	}
    
    	// function to be executed on clicking "cancelButton"
    	private void CancelClicked()
    	{
    		gui.RemoveChild(window);
    	}
    
    	// function to be executed on clicking "okButton"
    	private void OkClicked()
    	{
    		// quit the application
    		Engine.Quit();
    	}
    
    }
  2. Assign the VRHandMenu.cs component to the menu_attached_to_hand node. And disable the VRMenuSample.cs component which is assigned to this node by default.Назначьте компонент VRHandMenu.cs на ноду menu_attached_to_hand. Затем отключите компонент VRMenuSample.cs, который назначен на эту ноду по умолчанию.

  3. Save changes (Ctrl+S) and click Play to run the application.Сохраните изменения (Ctrl+S) и нажмите Play для запуска приложения.

3.8. Adding a New Interactable Object
3.8. Добавление нового интерактивного объекта#

Let's add a new type of interactable object that we can grab, hold, and throw (i.e. it will be inherited from the VRBaseInteractable component) with an additional feature: some visual effect (for example, smoke) will appear when we grab the object and, it will disappear when we release the object. In addition, it will display certain text in the console, if the corresponding option is enabled.Давайте добавим новый тип интерактивного объекта, который мы можем брать, держать и бросать (т.е. он будет унаследован от компонента VRBaseInteractable), с дополнительной функцией: при захвате объекта будет появляться некоторый визуальный эффект (например, дым), который исчезнет, когда мы отпустим объект. Кроме того, будет отображаться определенный текст в консоли, если включена соответствующая опция.

  1. Create a new VRObjectVFX component and add the following code to it:Создайте новый компонент VRObjectVFX и добавьте к нему следующий код:

    VRObjectVFX.cs

    Исходный код (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component
    public class VRObjectVFX : VRBaseInteractable
    {
    	// an asset with an effect that occurs when the object is grabbed
    	public AssetLinkNode vfx_node = null;
    	private Node vfx = null;
    	private Unigine.Object obj = null;
    	
    	// this method is called for the interactable object each frame
    	private void Update()
    	{
    		// update transformation of the effect if the object is grabbed
    		if(CurrentState == VRBaseInteractable.INTERACTABLE_STATE.GRABBED)
    	vfx.WorldTransform = node.WorldTransform;
    	}
    
    	// this overridden method is called after component initialization
    	// (you can implement checks here)
    	protected override void OnReady()
    	{
    	// check if the asset with the visual effect is specified	
    	if (vfx_node == null)
    	{
    		Log.Error($"{nameof(VRObjectVFX)} error: 'vfx_node' is not assigned.\n");
    		Enabled = false;
    		return;
    	}
    	else
    	{
    			vfx = vfx_node.Load();
    			vfx.Enabled = false;
    	}
    	
    	}
    
    	// this overridden method is called when grabbing the interactable object
    	public override void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		// show the effect
    		vfx.Enabled = true;
    		// set the current object state to GRABBED
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.GRABBED;
    	}
    
    	// this overridden method is called when releasing the interactable object
    	public override void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller)
    	{
    		// hide the effect
    		vfx.Enabled = false;
    		// set the current object state to NOT_INTERACT
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.NOT_INTERACT;
    	}
    }
  2. Assign the VRObjectVFX.cs component to the cylinder node (the child of the kinematic_movable dummy node).Назначьте компонент VRObjectVFX.cs на ноду cylinder (дочерний элемент Node Dummy kinematic_movable).
  3. Drag the vr/particles/smoke.node asset to the Vfx Node field. This node stores the particle system representing the smoke effect. It is available in the vr/particles folder of UNIGINE Starter Course Projects add-on.Перетащите элемент vr/particles/smoke.node в поле Vfx Node. В этой ноде хранится система частиц, представляющая эффект дыма. Она доступна в папке vr/particles дополнения UNIGINE Starter Course Projects.

  4. Save changes (Ctrl+S) and press the Play button to run the application.Сохраните изменения (Ctrl+S) и нажмите кнопку Play для запуска приложения.

Now if you grab and hold the cylinder, it will emit smoke:Теперь, если вы возьмете цилиндр и будете держать его, из него пойдет дым:

4. Packing a Final Build
4. Упаковка финального билда#

Now, you can build the project. Specify the required settings and ensure the final build includes all content, code, and necessary libraries. Ensure all unused assets are deleted. And don't forget to enable the VR modes!Теперь вы можете создавать проект. Укажите необходимые настройки и убедитесь, что финальная сборка включает весь контент, код и необходимые библиотеки. Убедитесь, что все неиспользуемые ресурсы удалены. И не забудьте включить режимы виртуальной реальности!

To get more information on how to pack the final build, refer to the Programming Quick Start section.To get more information on how to pack the final build, refer to the Programming Quick Start section.

Where to Go From Here
Что дальше#

Congratulations! Now, you can continue developing the project on your own. Here are some helpful recommendations for you:Поздравляю! Теперь вы можете продолжить разработку проекта самостоятельно. Вот несколько полезных рекомендаций для вас:

  • Try to analyze the source code of the sample further to figure out how it works and use it as a reference to write your own code.Попробуйте еще раз проанализировать исходный код примера, чтобы понять, как он работает, и использовать его в качестве ориентира для написания собственного кода.
  • Read the Virtual Reality Best Practices article for more information and tips on preparing content for VR and enhancing the user experience.Прочтите статью Лучшие практики VR для получения дополнительной информации и советов по подготовке контента для виртуальной реальности и улучшению пользовательского опыта.
  • Learn more about the Component System by reading the Component System article.Прочтите статью Система компонентов для получения дополнительной информации о работе с системой компонентов.
  • Check out the Component System Usage Example for more details on implementing logic using the Component System.Ознакомьтесь с Примерами использования компонентной системы для получения более подробной информации о реализации логики с использованием компонентной системы.
Последнее обновление: 13.12.2024
Build: ()