Search Unity

[XR Input, Quest] How to get button inputs on Oculus Quest?

Discussion in 'VR' started by Wattosan, Apr 20, 2020.

  1. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    Hi,

    How can I get button inputs on the oculus quest? I need to detect the A,B, X, Y buttons.
    I found the CommonUsages class but it does not have usages for these cases. How can I detect inputs for these cases?

    I suppose I have to use inputDevice.TryGetFeatureValue() somehow. Should I enter the string value for A, B and etc?

    Thanks!
     
  2. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    Okay, I managed to get the button inputs.

    1. First, get the input devices.

    InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.Controller &
    InputDeviceCharacteristics.TrackedDevice, _inputDevices);

    2. Then check if it is the left or right controller (this could have already been filtered and cached when getting the devies).
    if (device.characteristics.HasFlag(InputDeviceCharacteristics.Left))

    3. Get the IsPressed value of InputHelpers.Button.PrimaryButton or the InputHelpers.Button.SecondaryButton. The primary button on the right hand controller is A and secondary button is B. For the left hand controller these are X and Y.

    But this is where I have the next issue. Currently I am using inputDevice.IsPressed() (
    if (inputDevice.IsPressed(InputHelpers.Button.PrimaryButton, out bool isPressedPrimaryButton) && isPressedPrimaryButton)
    )and then pass the buttons as arguments. However, this is true as long as the buttons are held down. How would I get the button down and button up events? I could implement this functionality on my own but from a complete input system I would expect to access these methods in a simple manner.

    Also, the OVRInput has an Axis1D struct to get the amount of how much the button has been pressed down (from 0 to 1). How can I get the 1D Axis value of these buttons in XRInput?

    Thanks!
     
    Last edited: Apr 20, 2020
  3. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    By going through the new VR escape room example made by Unity, I found how they solved it:
    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// InteractionState type to hold current state for a given interaction.
    4. /// </summary>
    5. internal struct InteractionState
    6. {
    7.     /// <summary>This field is true if it is is currently on.</summary>
    8.     public bool active;
    9.     /// <summary>This field is true if the interaction state was activated this frame.</summary>
    10.     public bool activatedThisFrame;
    11.     /// <summary>This field is true if the interaction state was de-activated this frame.</summary>
    12.     public bool deActivatedThisFrame;
    13. }
    14.  
    15. void HandleInteractionAction(XRNode node, InputHelpers.Button button, ref InteractionState interactionState)
    16. {
    17.     bool pressed = false;
    18.     inputDevice.IsPressed(button, out pressed, m_AxisToPressThreshold);
    19.              
    20.     if (pressed)
    21.     {
    22.         if (!interactionState.active)
    23.         {
    24.             interactionState.activatedThisFrame = true;
    25.             interactionState.active = true;
    26.         }
    27.     }
    28.     else
    29.     {
    30.         if (interactionState.active)
    31.         {
    32.             interactionState.deActivatedThisFrame = true;
    33.             interactionState.active = false;
    34.         }
    35.     }
    36. }

    This means that we would have to detect the ButtonDown and ButtonUp events ourselves if we wanted to create a custom controller. It would be really helpful if there was an extension method similar to inputDevice.IsPressed() such as .WasPressedDown or .WasReleased or .ButtonDown or .ButtonUp. Every other input system has these functionalities built in.
     
  4. CGPepper

    CGPepper

    Joined:
    Jan 28, 2013
    Posts:
    152
    Yeah, we got really spoiled by the build in Down and Up events.

    here is my code
    Code (CSharp):
    1.  
    2. //global
    3. private float _gripStrength;
    4. private bool _grabbingActive = false;
    5.  
    6. //OnUpdate
    7. _device.TryGetFeatureValue(CommonUsages.grip, out _gripStrength);
    8.  
    9. if (_gripStrength > 0.2f)
    10. {
    11.     if (!_grabbingActive)
    12.     {
    13.         _grabbingActive = true;
    14.         //press event
    15.     }
    16.  
    17.  
    18. } else if (_grabbingActive)
    19. {
    20.     _grabbingActive = false;
    21.     //release event
    22.  
    23. }
     
    Last edited: Apr 21, 2020
    coolguy2016 and Wattosan like this.
  5. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    Nice!

    I wrote a little bit different solution.

    Code (CSharp):
    1. private bool _leftTriggerDown;
    2. private bool _leftGripDown;
    3. // and other left hand buttons
    4.  
    5. private bool _rightTriggerDown;
    6. private bool _rightGripDown;
    7. // and other right hand buttons
    8.  
    9. private void Update()
    10. {
    11.     foreach (InputDevice inputDevice in _inputDevices)
    12.     {
    13.         if (inputDevice.characteristic.HasFlag(InputDeviceCharacteristic.Left))
    14.         {
    15.             // Left hand, grip button
    16.             ProcessInputDeviceButton(inputDevice, InputHelpers.Button.Grip, ref _leftTriggerDown,
    17.             () => // On Button Down
    18.             {
    19.                 Debug.Log("Left hand trigger down");
    20.                 // Your functionality
    21.             },
    22.             () => // On Button Up
    23.             {
    24.                 Debug.Log("Left hand trigger up");
    25.             });
    26.             // Repeat ProcessInputDeviceButton for other buttons
    27.         }
    28.         // Repeat for right hand
    29.     }
    30. }
    31.  
    32. private void ProcessInputDeviceButton(InputDevice inputDevice, InputHelpers.Button button, ref bool _wasPressedDownPreviousFrame, Action onButtonDown = null, Action onButtonUp = null, Action onButtonHeld = null)
    33. {
    34.     if (inputDevice.IsPressed(button, out bool isPressed) && isPressed)
    35.     {
    36.         if (!_wasPressedDownPreviousFrame) // // this is button down
    37.         {
    38.             onButtonDown?.Invoke();
    39.         }
    40.  
    41.         _wasPressedDownPreviousFrame = true;
    42.         onButtonHeld?.Invoke();
    43.     }
    44.     else
    45.     {
    46.         if (_wasPressedDownPreviousFrame) // this is button up
    47.         {
    48.             onButtonUp?.Invoke();
    49.         }
    50.  
    51.         _wasPressedDownPreviousFrame = false;
    52.     }
    53. }
     
    alexbrigham likes this.
  6. CGPepper

    CGPepper

    Joined:
    Jan 28, 2013
    Posts:
    152
    Ah yes, good solution by generalising with ProcessInputDeviceButton

    You could also automate the creation of the booleans with a list or a dictionary. Would make it even cleaner.
     
  7. alexbrigham

    alexbrigham

    Joined:
    Mar 15, 2016
    Posts:
    3

    Wattosan,

    Thank you for contributing that method! I have been using it as the basis for my Input System, although it seems to work best when it is called in Update() without the Foreach loop.

    I would really like to use the Foreach loop method, but when nested like this, the actions get called every frame resulting in the wrong effect. Are you having this experience as well? If not, how did you work around it?
     
  8. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    Hey!

    I am not having that issue. The actions are not getting called every frame. In the example code they get called only if the button is actually pressed down/held down or the button is let go. If the player is not pressing or holding down the controller buttons, the actions do not get called. This is assuming that by actions you refer to the 3 callback actions in the ProcessInputDeviceButton() method.
     
  9. alexbrigham

    alexbrigham

    Joined:
    Mar 15, 2016
    Posts:
    3

    Ah, you're correct. I tried reducing your method by passing in dictionary elements. (Mapping Hand--Button, to find the reference bool). But it turns out that c# doesn't support accessing ref variables via indexing (such as through a dictionary).

    So I tried changing the "ref bool" parameter to just "bool" in the ProcessInput method, but that breaks the method. Any idea how I could restructure the method to take a "bool" instead of a "ref bool". If not, can you help explain why a "ref bool" is required?

    Any help is appreciated! Thanks.
     
  10. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    The problem is that the inputDevice API does not have a method to get ButtonDown or ButtonUp events. Only IsPressed(). So you have implement the button up and button down cases yourself. The ref bool is used to check if the button was held down the previous frame as well. If it wasn't (line 36) but it is now (line 34) then it is the first frame the button is held down - meaning that we got the ButtonDown(). If it is held down (line 34) and it was held down the previous frame as well, we no longer get the button down case. Some logic applies to releasing the button. If the button is no longer pressed (line 44) but it was held down the previous frame (line 46) then we get the button up case.

    Using ref bool is just one way to do it. You could, use an array instead. Perhaps even in combination with a struct.

    An example to get rid of the ref bool, using an array and a struct:
    Code (CSharp):
    1.  
    2. private InputDeviceButton[] inputDeviceButtons;
    3.  
    4. private void Awake()
    5. {
    6.     // Init array here
    7.     // loop through input devices
    8.     // check if left or right
    9.     // add entries into the array (or use a list)
    10. }
    11.  
    12. private void Update()
    13. {
    14.     foreach (InputDeviceButton inputDeviceButton in inputDeviceButtons)
    15.     {
    16.         ProcessInputDeviceButton(inputDeviceButton);
    17.     }
    18. }
    19.  
    20. private void ProcessInputDeviceButton(InputDeviceButton inputDeviceButton)
    21. {
    22.     if (inputDeviceButton.inputDevice.IsPressed(inputDeviceButton.button, out bool isPressed) && isPressed)
    23.     {
    24.         if (!inputDeviceButton.wasPressedDownPreviousFrame) // // this is button down
    25.         {
    26.             inputDeviceButton.onButtonDown?.Invoke();
    27.         }
    28.  
    29.         inputDeviceButton.wasPressedDownPreviousFrame = true;
    30.         inputDeviceButton.onButtonHeld?.Invoke();
    31.     }
    32.     else
    33.     {
    34.         if (inputDeviceButton.wasPressedDownPreviousFrame) // this is button up
    35.         {
    36.             inputDeviceButton.onButtonUp?.Invoke();
    37.         }
    38.  
    39.         inputDeviceButton.wasPressedDownPreviousFrame = false;
    40.     }
    41. }
    42.  
    43. private struct InputDeviceButton
    44. {
    45.     public InputDevice inputDevice;
    46.     public InputHelpers.Button button;
    47.     public bool wasPressedDownPreviousFrame
    48.     public Action onButtonDown;
    49.     public Action onButtonUp;
    50.     public Action onButtonHeld;
    51.  
    52.     public InputDeviceButton(InputDevice inputDevice, InputHelpers.Button button, bool wasPressedDownPreviousFrame, Action onButtonDown, Action onButtonUp, Action onButtonHeld)
    53.     {
    54.         this.inputDevice = inputDevice;
    55.         this.button = button;
    56.         this.wasPressedDownPreviousFrame = wasPressedDownPreviousFrame;  
    57.         this.onButtonDown = onButtonDown;
    58.         this.onButtonUp = onButtonUp;
    59.         this.onButtonHeld = onButtonHeld;
    60.     }
    61. }
    62.  
     
    alexbrigham likes this.
  11. alexbrigham

    alexbrigham

    Joined:
    Mar 15, 2016
    Posts:
    3
    Hi Wattosan, thank you for all of your help! I tried implementing it with the struct method you suggested, but ultimately ran into the same problem of having my actions called repeatedly each frame. At the very least, I learned something from your code.

    I ended up solving my ref bool issue by creating a Boolean wrapper class and then having ProcessInputDeviceButton() take a Boolean instead of a ref bool. Using this method allowed me to get a reference to the corresponding button booleans, while also being able to access them through dictionaries. (Unlike with ref bools).


    Code (CSharp):
    1.  
    2.  
    3. //Boolean Wrapper Class
    4. public class Boolean
    5. {
    6.         public bool Value;
    7.         public Boolean(bool value)
    8.         {
    9.             Value = value;
    10.         }
    11. }
    12.  
    13.  
    14. //Boolean construction and reference example:
    15. public static Boolean right_grip;
    16. public static Boolean left_thumbStickPress;
    17.  
    18. void Start()
    19. {
    20.     right_grip = new Boolean(false);
    21.     left_thumbStickPress = new Boolean(false);
    22. }
    23. ...etc for rest of the Booleans.
    24.  
    25.  
    26. //Nested dictionaries to store the mappings (Takes the XR Node and Button to access the reference Boolean)
    27. deviceButtonToBoolDict = new Dictionary<XRNode, Dictionary<InputHelpers.Button, Boolean>>()
    28. {
    29.     { RightHand, new Dictionary<InputHelpers.Button, Boolean>()
    30.            { InputHelpers.Button.Primary2DAxisClick, right_thumbStickPress }
    31.     },
    32.      { LeftHand, new Dictionary<InputHelpers.Button, Boolean>()
    33.            { InputHelpers.Button.Primary2DAxisClick, left_thumbStickPress }
    34.      }
    35. });
    36. etc for rest of mappings...
    37.  
    38.  
    39. //And then I call your method in Fixed Update like so:
    40.  
    41. foreach (XRNode node in InputManager.xrNodes)
    42. {
    43.     var triggerBool = InputManager.deviceButtonToBoolDict[node][InputHelpers.Button.Trigger];
    44.  
    45.     InputManager.ProcessInputDeviceButton(InputDevices.GetDeviceAtXRNode(node), InputHelpers.Button.Trigger, triggerBool, null, null,
    46. () => //On Button Held
    47. {
    48.        //Do something
    49. });
    50.  

    This method reduces unnecessary code and works as intended for both hands. I will leave this code block here for anyone who is running into the same problem that I did. Once again, thanks for all of your help!