Смена материала лазерной указкой
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.Предположим, мы хотим расширить функциональность лазерной указки в нашем шаблоне, которую на данный момент мы можем захватывать, удерживать, бросать и использовать (включать луч), добавив дополнительное действие – циклическая смена материалов по заданному списку на объекте, на который указывает луч (мы уже делали подобное в самом первом ArchViz-проекте). Это действие будет выполняться при использовании (Grip) указки захваченной одним контроллером с зажатой боковой кнопкой использования (Grip) на противоположном контроллере.
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). Components inherited from VRBaseInteractable must implement overrides of these methods. For example, the logic for grabbing the object must be implemented in the OnGrabBegin method of the inherited component.Основным компонентом, определяющим базовый функционал всех интерактивных объектов, является VRBaseInteractable (vr_template/components/base/VRInteractable.cs). Именно здесь определяются возможные состояния интерактивных объектов (NOT_INTERACT, HOVERED, GRABBED, USED) и методы, вызываемые при определенных действиях с объектом (OnHoverBegin, OnHoverEnd, OnGrabBegin, OnGrabEnd, OnUseBegin, OnUseEnd). В унаследованных компонентах интерактивных объектов логика, которая должна выполняться при выполнении того или иного действия, реализуется в перегрузке соответствующего метода (например, при захвате объекта вызывается 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).
-
Add a new OnAltUse action (alternative use) and a new ALT_USED state to the VRBaseInteractable base component:Добавим новое действие OnAltUse (альтернативное использование) и новое состояние ALT_USED в базовый компонент VRBaseInteractable:
using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- идентификатор генерируется автоматически для компонента public class VRBaseInteractable : Component { static public event Action onInit; public enum INTERACTABLE_STATE { NOT_INTERACT, HOVERED, GRABBED, USED${#HL}$, ALT_USED // Добавляем новое состояние ${HL#}$ } 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) { } ${#HL}$// Добавляем новое действие и его прекращение public virtual void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { } ${HL#}$ }
-
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, когда соответствующие флаги установлены.
#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")] // <-- идентификатор генерируется автоматически для компонента 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; ${#HL}$private bool altuse = false; public List<Material> materials = null; private int current_material = 0; ${HL#}$ protected override void OnReady() { laserRayMat = new Mat4(laserRay.Transform); laser.Enabled = false; ${#HL}$// если список текстур не задан или пуст, сообщаем об ошибке if(materials == null || materials.Count < 0) { Log.Error($"{nameof(VRLaserPointer)} error: materials list is empty.\n"); Enabled = false; return; } ${HL#}$ } 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); ${#HL}$// реализация логики альтернативного действия (циклическая смена материала на объекте по списку) 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; } ${HL#}$ } 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; } ${#HL}$ // перегрузка метода, вызываемого при выполнении альтернативного действия с объектом public override void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { altuse = true; CurrentState = VRBaseInteractable.INTERACTABLE_STATE.ALT_USED; } // перегрузка метода, вызываемого при завершении альтернативного действия с объектом public override void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { altuse = false; CurrentState = VRBaseInteractable.INTERACTABLE_STATE.NOT_INTERACT; } ${HL#}$ }
-
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():
public override void Interact(VRInteractionManager.InteractablesState interactablesState, float ifps) { // ... // update current input bool grabDown = false; bool grabUp = false; bool useDown = false; bool useUp = false; ${#HL}$// сброс флагов перед проверкой текущего ввода bool altUse = false; bool altUseUp = false; ${HL#}$ 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); ${#HL}$// для левого контроллера должна удерживаться кнопка использования на правом altUse = VRInput.IsRightButtonPress(VRInput.ControllerButtons.USE_BUTTON); altUseUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${HL#}$ 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); ${#HL}$// для правого контроллера должна удерживаться кнопка использования на левом altUse = VRInput.IsLeftButtonPress(VRInput.ControllerButtons.USE_BUTTON); altUseUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${HL#}$ 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); ${#HL}$// для клавиатуры должна удерживаться кнопка JUMP (Прыжок) altUse = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.JUMP); altUseUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.JUMP); ${HL#}$ break; default: break; } ${#HL}$// прекращение альтернативного использования - вызываем 'OnAltUseEnd' // для всех компонент на выделенном (hovered) объекте, для которых он реализован if (altUseUp) { foreach (var hoveredObjectComponent in hoveredObjectComponents) hoveredObjectComponent.OnAltUseEnd(this, controller); } ${HL#}$ // 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); } } ${#HL}$// при выполнении условий альтернативного использования вызываем соответствующий метод (OnAltUse) // для всех компонент на выделенном (hovered) объекте, для которых этот метод реализован if (altUse) { foreach (var hoveredObjectComponent in hoveredObjectComponents) if (hoveredObjectComponent.CurrentState != VRBaseInteractable.INTERACTABLE_STATE.ALT_USED) hoveredObjectComponent.OnAltUse(this, controller); altUse = false; } ${HL#}$ } // ... }
-
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. В компоненте VRLaserPointer для массива materials задаем количество элементов (например, 3) и перетаскиваем подходящие материалы из окна Materials, заполняя ячейки массива.
- Save changes (Ctrl+S) and press the Play button to run the application.Сохраните мир (Ctrl+S) и нажмите кнопку Play.