Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved While hovering over UI, how do I get the controller being used?

Discussion in 'XR Interaction Toolkit and Input' started by MaskedMouse, Nov 12, 2021.

  1. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,058
    The Quest has 2 controllers, which both are being used to interact with the UI.
    The Haptics events on the
    XRRayInteractor
    do not trigger. All of the
    XRBaseInteractable
    don't fire events when they're attached on UI components.
    I guess the Event system is not written to fire off those events on UI. Only on physical objects.

    So what I am trying to do is whenever I hover over UI that is interactable, I want the controller to give off a short little vibration. Like the Quest does when you're navigating through the menu's

    I'd like to make it a component so it can be managed what and what not interaction should vibrate.
    But however, when implementing the IPointerEnter / Exit you're going to need the Input Device to actually trigger haptic feedback. And this haptic feedback should happen on the controller being used. But the event given does not have this information.

    So is there a simple way to do this other than creating a custom input module just to fire off those Hover events? And also something that does not involve checking an Update loop every frame.


    Edit:
    I have adapted it a bit of what @C-Through suggested.

    Code (CSharp):
    1. public class XRHapticFeedbackComponent : MonoBehaviour, IPointerEnterHandler
    2. {
    3.     public float FeedBackForce = 0.4f;
    4.     public float Duration = 0.02f;
    5.  
    6.     private XRUIInputModule GetXRInputModule() => EventSystem.current.currentInputModule as XRUIInputModule;
    7.  
    8.     private bool TryGetXRRayInteractor(int pointerID, out XRRayInteractor rayInteractor)
    9.     {
    10.         var inputModule = GetXRInputModule();
    11.         if (inputModule == null)
    12.         {
    13.             rayInteractor = null;
    14.             return false;
    15.         }
    16.  
    17.         rayInteractor = inputModule.GetInteractor(pointerID) as XRRayInteractor;
    18.         return rayInteractor != null;
    19.     }
    20.  
    21.     public void OnPointerEnter(PointerEventData eventData)
    22.     {
    23.         if (TryGetXRRayInteractor(eventData.pointerId, out var rayInteractor))
    24.         {
    25.             rayInteractor.SendHapticImpulse(FeedBackForce, Duration);
    26.         }
    27.     }
    28. }
     
    Last edited: Nov 16, 2021
    kraskon and mrfossy like this.
  2. C-Through

    C-Through

    Joined:
    Aug 18, 2016
    Posts:
    4
    I honestly have no idea how I figured this out. But, this works and is implemented as you described, I imagine you can also do it with the pointer-events from the Input Module directly.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.XR.Interaction.Toolkit;
    4. using UnityEngine.XR.Interaction.Toolkit.UI;
    5.  
    6. public class HoverHaptic : MonoBehaviour, IPointerEnterHandler
    7. {
    8.     private XRUIInputModule InputModule => EventSystem.current.currentInputModule as XRUIInputModule;
    9.  
    10.     public void OnPointerEnter(PointerEventData eventData)
    11.     {
    12.         XRRayInteractor interactor = InputModule.GetInteractor(eventData.pointerId) as XRRayInteractor;
    13.         interactor.xrController.SendHapticImpulse(0.25f, 0.25f);
    14.     }
    15. }
     
  3. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,058
    It is a very clever solution

    Getting the XR Input Module through the EventSystem to then get the Interactor via the pointer ID.
    I had actually not even thought of that. I'm so used to the Event data being all I need that I didn't even think of requesting a reference from the XR Input Module like this.
     
    mrfossy and C-Through like this.
  4. mrfossy

    mrfossy

    Joined:
    Mar 11, 2019
    Posts:
    4
    This worked perfectly for me. Thought I'd never get haptics working. Thank you very much—both of you! :)
     
  5. AzurySimon

    AzurySimon

    Joined:
    Jul 16, 2018
    Posts:
    38
    Another way to do this would be to check the eventData itself by casting to TrackedDeviceEventData:
    Code (CSharp):
    1. public void OnPointerEnter(PointerEventData eventData)
    2. {
    3.     if (eventData is TrackedDeviceEventData trackedDeviceEventData)
    4.     {
    5.         if (trackedDeviceEventData.interactor is XRBaseControllerInteractor xrInteractor)
    6.         {
    7.             xrInteractor.SendHapticImpulse(0.25f, 0.25f);
    8.         }
    9.     }
    10. }
    Using the pattern matching magic of C# 8 this can be shortened to:
    Code (CSharp):
    1. public void OnPointerEnter(PointerEventData eventData)
    2. {
    3.     if (eventData is TrackedDeviceEventData { interactor: XRBaseControllerInteractor xrInteractor })
    4.     {
    5.         xrInteractor.SendHapticImpulse(0.25f, 0.25f);
    6.     }
    7. }
     
    Last edited: Apr 6, 2022
    TzuriTeshuba and MaskedMouse like this.
  6. sfct

    sfct

    Joined:
    Nov 7, 2022
    Posts:
    1
    hi guys! where should i put the script in editor?
     
  7. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    I put it in a script at the root object of any UI of interest. I added this all after i had my menus set up and the buttons weren't prefabbed, so this is a nice solution. only downside is that it adds it all at runtime. you could add an EventTrigger component in the inspector though and manuall do what my code is doing.

    Code (CSharp):
    1.     void Awake()
    2.     {
    3.         buttons = GetComponentsInChildren<Button>(true);
    4.         AddEventsComponents();
    5.     }
    6.  
    7.     void AddEventsComponents(){
    8.         for(int i = 0; i < buttons.Length; i++){
    9.             Button btn = buttons[i];
    10.  
    11.             EventTrigger trigger = btn.gameObject.AddComponent<EventTrigger>();
    12.  
    13.             //hover enter
    14.             EventTrigger.Entry hoverEnterEntry = new EventTrigger.Entry();
    15.             hoverEnterEntry.eventID = EventTriggerType.PointerEnter;
    16.             hoverEnterEntry.callback.AddListener(HandleHoverEnter);
    17.             trigger.triggers.Add(hoverEnterEntry);
    18.         }
    19.     }
    20.  
    21.     void HandleHoverEnter(BaseEventData eventData = null){
    22.  
    23.         if (eventData is TrackedDeviceEventData trackedDeviceEventData)
    24.         {
    25.             if (trackedDeviceEventData.interactor is XRBaseControllerInteractor xrInteractor)
    26.             {
    27.                 xrInteractor.SendImpulse(.1f, .1f);
    28.             }
    29.         }
    30.     }
     
  8. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    161
    For anyone like me finding this thread trying to doing it with Oculus Interaction SDK I found a working solution. Hopefully it works for you.

    You attach it to the Unity Canvas GameObject that's already set up with a RayInteractable/PointableCanvas Make sure you have a PointableCanvasModule in your scene.

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using Oculus.Interaction;
    5. using Oculus.Interaction.Input;
    6. using UnityEngine;
    7.  
    8. [RequireComponent(typeof(Canvas))]
    9. public class RayCanvasHapticPulse: MonoBehaviour
    10. {
    11.  
    12.     [Range(0,1)]
    13.     [SerializeField] private float _pulseAmplitude = 0.1f;
    14.    
    15.     private RayInteractor[] _rayInteractors;
    16.     private Canvas _canvas;
    17.  
    18.     private void Awake()
    19.     {
    20.         _canvas = GetComponent<Canvas>();
    21.         _rayInteractors = FindObjectsOfType<RayInteractor>();
    22.     }
    23.  
    24.     private void OnEnable()
    25.     {
    26.         PointableCanvasModule.WhenSelectableHovered += Hovered;
    27.     }
    28.  
    29.     private void OnDisable()
    30.     {
    31.         PointableCanvasModule.WhenSelectableHovered -= Hovered;
    32.     }
    33.  
    34.     private void Hovered(PointableCanvasEventArgs eventArgs)
    35.     {
    36.         if (eventArgs.Canvas != _canvas)
    37.         {
    38.             return; // wrong canvas
    39.         }
    40.        
    41.         foreach (RayInteractor rayInteractor in _rayInteractors)
    42.         {
    43.             if (rayInteractor.State == InteractorState.Hover)
    44.             {
    45.                 Controller controller = rayInteractor.GetComponentInParent<Controller>();
    46.                 Debug.Log($"Hand {controller.Handedness}");
    47.                 switch (controller.Handedness)
    48.                 {
    49.                     case Handedness.Left:
    50.                         StartCoroutine(Pulse(OVRInput.Controller.LTouch));
    51.                         break;
    52.                     case Handedness.Right:
    53.                         StartCoroutine(Pulse(OVRInput.Controller.RTouch));
    54.                         break;
    55.                     default:
    56.                         throw new ArgumentOutOfRangeException();
    57.                 }
    58.             }
    59.         }
    60.     }
    61.  
    62.     private IEnumerator Pulse(OVRInput.Controller controller)
    63.     {
    64.         OVRInput.SetControllerVibration(0, _pulseAmplitude, controller);
    65.         yield return null;
    66.         OVRInput.SetControllerVibration(0, 0, controller);
    67.     }
    68. }
    69.