Search Unity

Resolved Haptic feedback in XR

Discussion in 'XR Interaction Toolkit and Input' started by saifshk17, Nov 25, 2020.

  1. saifshk17

    saifshk17

    Joined:
    Dec 4, 2016
    Posts:
    488
    Hi,

    Is there an explanation of how to achieve haptic feedback in XR.Interaction through a script?
     
    MrDavidBlaine likes this.
  2. matty0187

    matty0187

    Joined:
    Oct 14, 2020
    Posts:
    3
    +1 I’d like to see an example of this too
     
  3. saifshk17

    saifshk17

    Joined:
    Dec 4, 2016
    Posts:
    488
    In XR controller script you will find the below function:

    Code (CSharp):
    1. public override bool SendHapticImpulse(float amplitude, float duration)
    2.         {
    3.             if (inputDevice.TryGetHapticCapabilities(out var capabilities) &&
    4.                 capabilities.supportsImpulse)
    5.             {
    6.                 return inputDevice.SendHapticImpulse(0u, amplitude, duration);
    7.             }
    8.             return false;
    9.         }
    So assuming you want to call this function from your own script, do like this:
    Code (CSharp):
    1. private XRController xr;
    2.  
    3. void Start()
    4. {
    5.   xr = (XRController) GameObject.FindObjectOfType(typeof(XRController));
    6. }
    7.  
    8. void ActivateHaptic()
    9. {
    10.   xr.SendHapticImpulse(0.7f, 2f);
    11. }
     
    MrDavidBlaine and wm-VR like this.
  4. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    To add on to the previous response, that
    SendHapticImpulse
    method is part of the base
    XRBaseController
    class, so it will work for both the recommended Action-based Controller (
    ActionBasedController
    ) and the Device-based Controller (
    XRController
    ).

    There is also a convenience method on
    XRBaseControllerInteractor
    with the same signature which is used within that same class for doing haptic feedback on select and hover if you would like to look at an example.
     
  5. louspawn10

    louspawn10

    Joined:
    Dec 5, 2018
    Posts:
    6
    I am using Unity version 2020.2.1f1 and the above doesn't work when making a build on Oculus Quest even though it works on the Editor as a Rift using Oculus link. I get zero haptic feedback, any ideas? Does anyone else have the same problem?
     
  6. AstralStew

    AstralStew

    Joined:
    Jun 13, 2013
    Posts:
    10
    Yep, exact same behaviour here on Unity 2019.4.11f. Using ADB it seems that InputDevice.TryGetHapticCapabilities returns false on the Quest, but not in editor as Rift.
     
    wimachtendink likes this.
  7. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    We don't know of any issues with haptic impulses not working with Quest. Please report this bug using Help > Report a Bug in Unity if you have a reproducible example.
     
  8. unity_Vvl3Urv06lpDmQ

    unity_Vvl3Urv06lpDmQ

    Joined:
    Mar 23, 2021
    Posts:
    1
    Same problem here. I figured out why.
    upload_2021-4-9_19-3-44.png

    with OpenXR it doesnt work for me. had to go back to Oculus
     
  9. NemesisWarlock

    NemesisWarlock

    Joined:
    Jan 21, 2017
    Posts:
    141
    That would be because, as stated in the Documentation for the OpenXR plugin, Haptics are not currently supported yet.
     
    chris-massie likes this.
  10. unity_f_BuZ-n1qXNO1A

    unity_f_BuZ-n1qXNO1A

    Joined:
    Apr 17, 2021
    Posts:
    1
    thanks
    I adjusted to what I needed, I made another script and implemented it in the righthandcontroller game object which contained the XRController class which was set for the right hand. so i get haptic for hand controller(Right Controller). I replaced: xr = (XRController)GameObject.FindObjectOfType(typeof(XRController));

    With: xr = GetComponent<XRController>();
     
  11. teddysgame

    teddysgame

    Joined:
    Jun 17, 2021
    Posts:
    10
    Hi, I'm fairly new to XR development. Can someone share a simple script so that I can get a better understanding? I tried following this but I am getting 'XR Controller' not found error. I tried looking into the XR Controller script but I have no idea how to call it's function.
     
  12. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    You can make a public field or add the
    SerializeField
    attribute to a non-public field in your script to make the component reference appear in the Inspector window. See the docs page about Variables and the Inspector.

    Add this example script to a GameObject and click the circle icon in the object field to select the controller in your scene to make vibrate. That will allow you call the XRBaseController.SendHapticImpulse method.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3. using UnityEngine.XR.Interaction.Toolkit;
    4.  
    5. public class HapticExample : MonoBehaviour
    6. {
    7.     // Adding the SerializeField attribute to a field will make it appear in the Inspector window
    8.     // where a developer can drag a reference to the controller that you want to send haptics to.
    9.     [SerializeField]
    10.     XRBaseController controller;
    11.  
    12.     protected void Start()
    13.     {
    14.         if (controller == null)
    15.             Debug.LogWarning("Reference to the Controller is not set in the Inspector window, this behavior will not be able to send haptics. Drag a reference to the controller that you want to send haptics to.", this);
    16.  
    17.         StartCoroutine(StartPeriodicHaptics());
    18.     }
    19.  
    20.     IEnumerator StartPeriodicHaptics()
    21.     {
    22.         // Trigger haptics every second
    23.         var delay = new WaitForSeconds(1f);
    24.  
    25.         while (true)
    26.         {
    27.             yield return delay;
    28.             SendHaptics();
    29.         }
    30.     }
    31.  
    32.     void SendHaptics()
    33.     {
    34.         if (controller != null)
    35.             controller.SendHapticImpulse(0.7f, 0.1f);
    36.     }
    37. }
     
  13. KillerFirefly

    KillerFirefly

    Joined:
    Jan 11, 2013
    Posts:
    1
    I have a tool which could be picked up by either hand, so specifying one controller for haptic didn't exactly make sense. I stumbled upon this way of doing it, which finds whichever controller is holding the object and sends the haptic to that controller.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.XR.Interaction.Toolkit;
    3.  
    4. [RequireComponent(typeof(XRBaseInteractable))]
    5. public class HapticFeedback : MonoBehaviour {
    6.  
    7.     public void SendHaptics() {
    8.         XRBaseControllerInteractor hand = (XRBaseControllerInteractor)GetComponent<XRBaseInteractable>().selectingInteractor;
    9.         hand.SendHapticImpulse(0.4f, 0.1f);
    10.     }
    11.  
    12. }
    I'm calling this SendHaptics function from within the activate unityevent on the XRGrabInteractable component placed on the object / tool.

    For example, say you have a gun, and it should send haptic when you pull the trigger. This way it works with both hands, and the rumble only happens on whichever hand is holding the gun.
     
    jessworoniak and PatHightree like this.
  14. leaf_is_not_leah

    leaf_is_not_leah

    Joined:
    May 24, 2021
    Posts:
    2
    Mine is working, but I want the haptics to vibrate like racing heartbeat, with short stops in between vibrations. Is it achievable? I tried playing with the duration and amplitude but it's just the same constant vibration with different strengths that's all :/
    Here's my code. Any hints or help would be greatly appreciated!
    Cheers.
    Code (CSharp):
    1. private IEnumerator HapticPattern(){
    2.         while(true){
    3.        
    4.             LeftHand.SendHapticImpulse(0.7f, 1f);
    5.             RightHand.SendHapticImpulse(0.7f, 1f);
    6.  
    7.             yield return new WaitForSeconds(2f);
    8.         }
    9.     }
     
  15. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    The first parameter to
    SendHapticImpulse
    is the amplitude value (between 0f and 1f), and the second parameter is the duration value in seconds. If you want to adjust the duration to create short vibrations, you should be able to adjust that second value lower to make shorter pulses.

    There's only one motor channel, so if another script also calls
    SendHapticImpulse
    or if you're using the Haptic Events in the Inspector of an interactor component, it could be overriding the haptic pattern from your script.
     
  16. ElectricYtArt

    ElectricYtArt

    Joined:
    Apr 5, 2022
    Posts:
    2
    The class thing
    It says class type or script invalid
     
  17. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    Make sure you name the script
    HapticExample
    in your Project window. MonoBehaviours require that the file name and the class name match.

    If that's not the error you're getting, can you say which Unity Editor version and XR Interaction Toolkit package version you're using, and where you're seeing that error?
     
  18. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
  19. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    Are you sure you have a valid
    InputDevice
    in your script? When I tested with a Quest 2 on Unity 2021.3.5f1 and the latest package versions (Oculus XR Plugin 3.0.2, XR Plugin Management 4.2.1), it was working.
     
  20. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
    Yes I'm sure its correct. It seems to only happen with Rift S FYI. Doesn't happen with Quest 2 over link cable.
    Identical code used for both. So seems to be an issue with Unity3D or Oculus libs Unity is using.
     
  21. VRDave_Unity

    VRDave_Unity

    Unity Technologies

    Joined:
    Nov 19, 2021
    Posts:
    277
    Hey!

    So we can't find any code changes on Unity's Oculus XR Plugin in the past 2 years that would affect the haptics code in any way. What version of the package are you upgrading from? We could use that to check the code for changes. This looks like something on the Oculus side, since the only thing changed in the last month was an update to v41 of the Oculus dll.
     
  22. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
    Hey! Ya very possible its on the OVR side & nothing to do with Unity.
    Outside a native app though I have no way to confirm this easily.

    Unity 2021.3.5f
    OculusXR 3.0.2
     
  23. Tovey-Ansell

    Tovey-Ansell

    Joined:
    Jul 8, 2015
    Posts:
    150
    Sorry to dig up an old thread, but we're not able to get Haptics to work at all on RiftS using the above method. Works fine on the Quest2 but not RiftS

    We've tried messing with the amplitude and duration, calling the SendHapticImpulse function only once, or every frame, no luck at all. Again, works fine on Quest 2, but not on RiftS (other apps' haptics work fine on the RiftS)

    Does anyone have any ideas? Is there something different you need to do for the RiftS controllers?
     
  24. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
    You need to use Oculus APIs directly for it to work well.

    Here is an example.
    https://github.com/VRStudios/Unity3...Assets/VRstudios/XRInput/API/OculusXR.cs#L138
     
  25. CreepyLamppost

    CreepyLamppost

    Joined:
    Jun 26, 2017
    Posts:
    20
    I'd like to add a bump, I'm in Unity 2019.4.6f1 and using XRITK 2.1.1 and OculusXR 1.3.4, and I also get the haptic feedback when I use Oculus Link but don't get it when I actually build the .apk to the Quest. I'm trying to use xrController.SendHapticImpulse(). Any reason this happens?
     
  26. VRDave_Unity

    VRDave_Unity

    Unity Technologies

    Joined:
    Nov 19, 2021
    Posts:
    277
    Are you using legacy input or the new Input System package? I would recommend updating to a newer version of OculusXR plugin and Unity Editor. I was able to successfully test that Haptics work on Quest 2 with 2019.4.40f and OculusXR 1.5.1. You can also manually update to a newer version of XRI by modifying the manifest directly (I tested 2.2.0).

    I also put together a quick test script you can drop on an Interactor or Interactable to send an Impulse from either direction of the interaction.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.XR.Interaction.Toolkit;
    3.  
    4. public class CodedHapticTest : MonoBehaviour
    5. {
    6.     XRBaseInteractable interactable;
    7.     XRBaseControllerInteractor interactor;
    8.  
    9.     void Start()
    10.     {
    11.         if (TryGetComponent<XRBaseInteractable>(out interactable))
    12.         {
    13.             interactable.hoverEntered.AddListener(StartHover);
    14.         }
    15.         if (TryGetComponent<XRBaseControllerInteractor>(out interactor))
    16.         {
    17.             interactor.hoverEntered.AddListener(StartHover);
    18.         }
    19.     }
    20.  
    21.     private void OnDisable()
    22.     {
    23.         if (interactable != null)
    24.             interactable.hoverEntered.RemoveListener(StartHover);
    25.  
    26.         if (interactor != null)
    27.             interactor.hoverEntered.RemoveListener(StartHover);
    28.     }
    29.  
    30.     public void StartHover(HoverEnterEventArgs args)
    31.     {
    32.         var interactor = args.interactorObject as XRBaseControllerInteractor;
    33.         if (interactor != null)
    34.         {
    35.             Debug.Log("Sending Haptic Impulse...");
    36.             interactor.SendHapticImpulse(1f, 0.08f);
    37.         }
    38.     }
    39. }
    40.  
     
    Verdant88 and zezba9000 like this.
  27. saifshk17

    saifshk17

    Joined:
    Dec 4, 2016
    Posts:
    488
    Is there a way to disable the haptic feedback?
    I am using xr simple interactable script where I have to enter in hover state, but I would like to disable the haptic feedback. How to do this?
     
  28. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
    You just set values to zero.
     
  29. ChloeCSL

    ChloeCSL

    Joined:
    May 6, 2023
    Posts:
    2
    Sorry to revive this thread again, but I can't seem to get haptic feedback on my PICO 4 controllers. I'm using Unity 2020.3.25f1 and the PICO Unity Integration SDK v2.x.x. I'm also quite new to Unity development and would appreciate some advice! :)

    Essentially, I want haptic feedback that is called on through a method rather than input on the controllers. This is an excerpt from my script. I have my LeftBaseController and RightBaseController assigned in the inspector window but I still can't get any haptic feedback. I know the OnCollisionEnter() is called correctly because I have other visual cues that tell me that it works. Have been trying to solve this for days...

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.XR.Interaction.Toolkit;
    3.  
    4. public class MoveHelicopter : MonoBehaviour
    5. {
    6.     public XRBaseController leftController, rightController;
    7.     public float defaultAmplitude = 1f;
    8.     public float defaultDuration = 1f;
    9.  
    10.     private void Start()
    11.     {
    12.         leftController = GetComponent<XRController>();
    13.         rightController = GetComponent<XRController>();
    14.     }
    15.  
    16.     private void OnCollisionEnter(Collision collision)
    17.     {
    18.         if (collision.gameObject.CompareTag("Ground") && !hasCollided)
    19.         {
    20.             SendHaptics();
    21.         }
    22.     }
    23.  
    24.     public void SendHaptics()
    25.     {
    26.         leftController.SendHapticImpulse(defaultAmplitude, defaultDuration);
    27.         rightController.SendHapticImpulse(defaultAmplitude, defaultDuration);
    28.     }
    29. }
     
  30. ChloeCSL

    ChloeCSL

    Joined:
    May 6, 2023
    Posts:
    2
    Oh nevermind. Seems like restarting the whole setup allowed the haptics to work!
     
  31. zezba9000

    zezba9000

    Joined:
    Sep 28, 2010
    Posts:
    992
    FYI from my experience Pico 3/4 have sometimes had haptic or tracking orientation bugs.
    Where target haptic controller would always target right controller.
    Or tracking velocities didn't match orientation correctly.

    Maybe they have been fixed since I last tested though.
     
    ChloeCSL likes this.
  32. Foreman_Dev

    Foreman_Dev

    Joined:
    Feb 4, 2018
    Posts:
    82
    How do we send buffered haptics via the new input system? I want to have fine-grained control over a haptic FX pattern, but the 'SendHapticImpulse' command is far too simple for this because it can only send a constant amplitude over a fixed duration. I have a byte[] array which contains my haptic pattern, and I want to send that to the controller. It appears that no method exists for XRBaseController.SendBufferedHapticCommand.

    I actually tried to implement it myself via the following code where I am converting a Unity animation curve into a haptic buffered rumble byte[] array.
    Code (CSharp):
    1. public void DebugSendBufferedRumble(AnimationCurve hapticAnimationCurve)
    2. {
    3.     if (xrController.hapticDeviceAction.action.activeControl?.device is XRControllerWithRumble rumbleController)
    4.     {
    5.         byte[] bufferedRumbleBytes = AnimationCurveToBufferedRumble(hapticAnimationCurve);
    6.         var bufferedRumbleCommand = SendBufferedHapticCommand.Create(bufferedRumbleBytes);
    7.         rumbleController.ExecuteCommand(ref bufferedRumbleCommand);
    8.     }
    9. }
    but when I execute the above code, the controller doesn't vibrate at all (I'm using Quest 3 controllers and testing in the Unity Editor via Quest Link). I've verified my byte[] array for the haptic effect is indeed filled correctly. But the controller doesn't vibrate. Is this a missing feature? @VRDave_Unity @chris-massie Or am I approaching this problem the wrong way?

    Following a similar pattern to the above, this code works just fine to send a haptic impulse:
    Code (CSharp):
    1. var command = SendHapticImpulseCommand.Create(0, 1.0f, 0.25f);
    2. rumbleController.ExecuteCommand(ref command);
    But this code does not work at all to vibrate the controller via buffered rumble:
    Code (CSharp):
    1. byte[] bufferedRumbleBytes = AnimationCurveToBufferedRumble(hapticAnimationCurve);
    2. var bufferedRumbleCommand = SendBufferedHapticCommand.Create(bufferedRumbleBytes);
    3. rumbleController.ExecuteCommand(ref bufferedRumbleCommand);
     
    Last edited: Nov 15, 2023
    chris-massie likes this.
  33. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    Buffered haptics are currently only supported with the Oculus Rift and only when using the Oculus XR Plugin package. The OpenXR Plugin package (currently as of 1.9.1) does not support buffered haptics at all.

    In Input System 1.8.0-pre.2 the
    GetHapticCapabilitiesCommand
    is now fixed and should return valid capability data, and the
    supportsBuffer
    will be false on the Quest 3 to indicate buffered haptics are not supported.

    So yes it is a missing feature.
     
  34. Foreman_Dev

    Foreman_Dev

    Joined:
    Feb 4, 2018
    Posts:
    82
    Hey @chris-massie, thanks for your response!

    I'm wondering if I could get your feedback about something: I ended up creating my own "pseudo-buffered haptics" system that converts a Unity animation curve into a bunch of rapid haptic impulses that get played every frame. It feels sort of "hacky" but it seems to work fine (at least for Quest controllers).

    Specifically, the haptic effect is created by constructing a Unity animation curve in the inspector, and then the haptics manager samples this curve every frame to get the current amplitude/intensity over time, and in a coroutine it sends a short-duration haptic impulse every frame at the correct amplitude. I suppose this could be considered a "streamed haptic". I found that any new haptic impulse overrides a currently-playing impulse (at least this is the behavior on Quest). So essentially you can "blast" the controller with haptic impulses every frame in order to achieve a dynamic haptic effect.

    Using this system, the duration of each haptic impulse needs to be at least as long as the frame time (so in 90Hz mode each impulse would be 0.01111 seconds long). But I found that it's best to use some "overlap" otherwise you feel an occasional stutter since the transmission of each impulse isn't perfectly timed. I went with a duration of 0.3333 seconds for every impulse, every frame. The extra time gets overridden by the impulse that plays on the next frame, so the overlap covers up any stutters or when the app experiences a drop in FPS.

    Is this sort of system viable for production? It seems to work perfectly for Quest controllers, but is this behavior consistent for other hardware? Specifically, will the most recent haptic impulse override any currently-playing haptic impulse for all XR controllers/hardware? I haven't had the opportunity to test this setup on Vive wands, Index, PSVR, or Vive Focus 3 controllers.

    P.S. - Someone else had a similar question here, but it was never answered.
     
  35. chris-massie

    chris-massie

    Unity Technologies

    Joined:
    Jun 23, 2020
    Posts:
    232
    What you described is exactly how I would have done it too. With the OpenXR Plugin, the underlying code that gets triggered just calls into xrApplyHapticFeedback with the vibration data stored in the XrHapticVibration argument. From the description on that first page:
    If you are using the Oculus Plugin or another one, it might be different and you would need to refer to the docs for those plugins or with Meta. Are you using OpenXR?
     
  36. Foreman_Dev

    Foreman_Dev

    Joined:
    Feb 4, 2018
    Posts:
    82
    Hey @chris-massie, thank you for your reply! I'm glad to hear the system I built was the "right way" to do it. :)

    I'm using the Oculus Plugin currently (although I use OpenXR for PC builds from the same project) and the Oculus Plugin appears to act the same way as described in the OpenXR docs, where the most recent haptic event interrupts a currently-playing haptic.

    So, this system should work for now! Although I would love to have greater control over haptics in the future, especially since Meta has developed new haptic technology for the Quest 3 controllers. I hope that the OpenXR spec eventually gets updated to support this. At least for now I can use haptic impulses tied to an animation curve in order to give haptic effects some "texture".