Search Unity

Resolved XR Interaction Toolkit throwing NullReferenceException?

Discussion in 'XR Interaction Toolkit and Input' started by arashi256, Nov 10, 2022.

  1. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    Hi, I am running into a problem with my XR Rig. I have multiple locomotion providers (snap/smooth turn, teleportation/smooth movement) and use an in-game menu to enable/disable each provider. Occasionally, Unity will throw up a NullReferenceException (image attached). What is going on here and has anybody come across anything similar? I'm pretty sure my code isn't throwing this exception. Any ideas?

    I am changing movment providers like so: -

    I was told on Discord to set boolean to false before setting it to true, which I have done but it doesn't seem to help.

    Code (CSharp):
    1. public class PlayerMovementType : MonoBehaviour
    2. {
    3.     public ActionBasedSnapTurnProvider snapTurn;
    4.     public ActionBasedContinuousTurnProvider continuousTurn;
    5.     public ActionBasedContinuousMoveProvider continuousMove;
    6.     public TeleportationProvider teleportationMove;
    7.     public GameObject leftTeleportationRay;
    8.     public GameObject rightTeleportationRay;
    9.     public TextMeshProUGUI movementSpeedOutput;
    10.     public TextMeshProUGUI turnSpeedOutput;
    11.     public TextMeshProUGUI turnIncOutput;
    12.     public TMP_Dropdown movementTypeDropdown;
    13.     public TMP_Dropdown turnTypeDropdown;
    14.     public Slider movementSpeedSlider;
    15.     public Slider turnSpeedSlider;
    16.     public Slider turnIncSlider;
    17.     public GameObject vignettePrefab;
    18.     public Toggle vignetteToggle;
    19.     public Slider vignetteAmountSlider;
    20.     public TextMeshProUGUI vignetteAmountOutput;
    21.  
    22.     public void SetTurnTypeFromIndex(int index)
    23.     {
    24.         if (index == 0)
    25.         {
    26.             continuousTurn.enabled = false;
    27.             snapTurn.enabled = false;
    28.             snapTurn.enabled = true;
    29.             snapTurn.enableTurnLeftRight = true;
    30.             snapTurn.enableTurnAround = true;
    31.         }
    32.         else if (index == 1)
    33.         {
    34.             snapTurn.enabled = false;
    35.             snapTurn.enableTurnLeftRight = false;
    36.             snapTurn.enableTurnAround = false;
    37.             continuousTurn.enabled = false;
    38.             continuousTurn.enabled = true;
    39.         }
    40.         PersistentPlayerSettings.instance.turnType = index;
    41.     }
    42.  
    43.     public void SetMovementTypeFromIndex(int index)
    44.     {
    45.         if (index == 0)
    46.         {
    47.             rightTeleportationRay.SetActive(true);
    48.             leftTeleportationRay.SetActive(true);
    49.             continuousMove.enabled = false;
    50.             teleportationMove.enabled = false;
    51.             teleportationMove.enabled = true;
    52.         }
    53.         else if (index == 1)
    54.         {
    55.             rightTeleportationRay.SetActive(false);
    56.             leftTeleportationRay.SetActive(false);
    57.             teleportationMove.enabled = false;
    58.             continuousMove.enabled = false;
    59.             continuousMove.enabled = true;
    60.         }
    61.         PersistentPlayerSettings.instance.movementType = index;
    62.     }
    63.  
    64.    ...
    65. }
    I don't think my code is to blame here, but I'm new to VR and not quite sure what is happening here. Surely I can't be the only person wanting to use multiple movement providers in a VR project, so I'm hoping somebody might have a fix or explainer.
     

    Attached Files:

  2. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    Hi Arashi... found your post while looking for reports of this. I can confirm encountering much the same issue. Interestingly it hasn't done it for a few days but then it through the exception while testing some 3 or 4 times in a row. With virtually no change to the code it worked again. So it seems that something is clearing the turn action reference. I was not switching move but rather continuous turn and snap turn.
     
  3. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    Hey - thanks for replying! Thank god, I'm not the only one! :D I found one other person on Discord with the same problem but it's so intermittent. I *think* I've solved it by never having both locomotion providers (for smooth/snap turn and teleportation/smooth movement) disabled at the same time.
     
  4. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    heh, heh, that would be me :) To others reading this, please note this isn't our failure to set some reference and wondering why it is null. The reference is to the Turn Action and (in my case) set in the editor both the Snap Turn and Continuous Turn reference the same action. It looks like that action reference gets cleared somehow when the provider is disabled.

    Arashi I see there is a Use Reference toggle setting. I'm curious if that can be set to true/false without clearing the actual reference.
     
  5. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    A quick note... I don't exactly know why I was setting individual turn settings. I've changed the code to enable/disable the TurnProvider instead. Also made sure (don't know if it matters) to turn one on before turning the other off.

    Code (CSharp):
    1.     public void SnapTurnOff()
    2.     {
    3.         ContinuousTurnProvider.enabled = true;
    4.         SnapTurnProvider.enabled = false;
    5.     }
    6.  
    7.  
    8.     public void SnapTurnOn()
    9.     {
    10.         SnapTurnProvider.enabled = true;
    11.         ContinuousTurnProvider.enabled = false;
    12.     }
     
  6. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    And if anyone from Unity is following this... it didn't seem to help. Worked fine several times then crashed the app. Then worked and then didn't work several times but didn't crash the app.
     
  7. VRDave_Unity

    VRDave_Unity

    Unity Technologies

    Joined:
    Nov 19, 2021
    Posts:
    275
    Hey @tleylan and @arashi256,

    Looking at the scenario and it looks like the main issue comes down to both turn providers using the same Action reference, correct? We ran into a similar issue when building some of the latest samples for XRI, which is why we split the Actions out to 2 separate ones.

    There are a couple of approaches that might work, but the easiest might be to create a 3rd component that references both components and acts as the toggle between them. It can check the if the Action references are equal between the 2 and when it disables one of the scripts, it can reenable the action reference that gets disabled. You will probably want a third state for the toggle that lets you turn both of them off. The other approach would be to create a derived class for both turn providers and override the OnEnable/OnDisable function to check if the other script is active and bypass disabling the action.

    Both of these may not be completely ideal and I will take this back to the team to iterate on a better solution for a future release or sample.
     
  8. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    That's the scenario... thanks for the clarification that we aren't crazy (well we might be). I use a script that represents my player. It makes the switch and I could reestablish the action reference there.

    You gave me an idea though. Would it do the same thing if we created an entirely new TurnSnap action? With the same settings but used by the Snap Provider? So each Turn provider has it's own (but identical) action defined.

    Thank you.
     
  9. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    Hi @VRDave_Unity
    Okay, I will play around with some of the proposed solutions and see what works. As long as you guys are aware of this issue for a future release!
     
  10. VRDave_Unity

    VRDave_Unity

    Unity Technologies

    Joined:
    Nov 19, 2021
    Posts:
    275
    @tleylan,
    If you created a new action so each reference is unique (but identical configuration), it will only disable the appropriate action (not the binding itself). This is the same solution we shipped in the XRI Default Input Action asset.
     
  11. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    I might do this instead. I just tested saving and then restoring the action and it seems to work well but I believe I would prefer not to do that. Thanks again for the info.
     
    VRDave_Unity likes this.
  12. boone0694

    boone0694

    Joined:
    Jan 7, 2018
    Posts:
    6
    Hi all, I'm experiencing the same problem. @VRDave_Unity I did what you suggested and set up two different actions, one for SnapTurn and one for ContinuousTurn. Then, on their respective TurnProvider, I put each different action.

    However, I'm still encountering the aforementioned error (mentioned in this thread). Does anyone have any thoughts?

    Input action bug .png



    Input action bug 2.png
     
  13. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    Well, because I'm none too bright, I went with the simpler option and simply stored the input actions and reapplied them when enabling/disabling the movement providers and whilst I cannot prove definitively that it's worked because the issue is so damned intermittent, I have yet to encounter this bug again. and I've been testing this quite aggressively as the nature of my code now means that the whole thing will crash on initialisation if it were still an issue for me. It's not pretty, but it appears to work (for me, at least).

    Like so: -

    Code (CSharp):
    1. // Saved Input Actions
    2. private InputActionProperty rightHandSnapTurnAction;
    3. private InputActionProperty rightHandContinuousTurnAction;
    4. private InputActionProperty leftHandSmoothMovementAction;
    5.  
    6. private void Awake()
    7. {
    8.     rightHandContinuousTurnAction = continuousTurn.rightHandTurnAction;
    9.     rightHandSnapTurnAction = snapTurn.rightHandSnapTurnAction;
    10.     leftHandSmoothMovementAction = continuousMove.leftHandMoveAction;
    11. }
    12.  
    13. ..
    14.  
    15. snapTurn.enabled = true;
    16. snapTurn.rightHandSnapTurnAction = rightHandSnapTurnAction;
    17. continuousTurn.enabled = false;
    18.  
    19. ..
    20.  
    21. continuousTurn.enabled = true;
    22. continuousTurn.rightHandTurnAction = rightHandContinuousTurnAction;
    23. snapTurn.enabled = false;
    24.  
    25. ..
    26.  
    27. rightTeleportationRay.SetActive(true);
    28. leftTeleportationRay.SetActive(true);
    29. teleportationMove.enabled = true;
    30. continuousMove.enabled = false;
    31.  
    32. ..
    33.        
    34. continuousMove.enabled = true;
    35. continuousMove.leftHandMoveAction = leftHandSmoothMovementAction;
    36. rightTeleportationRay.SetActive(false);
    37. leftTeleportationRay.SetActive(false);
    38. teleportationMove.enabled = false;
     
    Last edited: Dec 5, 2022
    boone0694 likes this.
  14. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    However, I'm still encountering the aforementioned error (mentioned in this thread). Does anyone have any thoughts?

    I tried creating two separate but equal actions and encountered the same error. I didn't want to experiment too much with it so I went back to the idea of reassigning the action to the one I have enabled. So far that seems to work.
     
  15. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    Hi Arashi... I might have been the source of your typo. Notice you have assumed that the continuousTurn.rightHandTurnAction is used for both? Reasonable in this case but not guaranteed.

    Code (CSharp):
    1. _continuousTurnAction = ContinuousTurnProvider.rightHandTurnAction;
    2. _snapTurnAction = SnapTurnProvider.rightHandSnapTurnAction;
    3.  
     
  16. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    Edited to fix typo :)
     
  17. boone0694

    boone0694

    Joined:
    Jan 7, 2018
    Posts:
    6
    Hi all, thank you for the insight. arashi256, I followed the exact structure you provided, in my own code of course, and still encountered this bug.

    I have tried these three different approaches:
    1. Create unique actions for snap/continuous turn, each with the same configuration, and assign those to the snap/continuous turn providers respectively (the way I presented above).
    2. Store the input actions on awake. Then when you change rotation type, assign the action to the new provider and disable the old provider (the way arashi256 presented above).
    3. Combine both of these solutions, i.e. use unique actions but also reassign that unique action to its own provider on rotation change. My idea here was that maybe the reference becomes bad so I just force a reference again.
    As others have mentioned as well, the bug is so intermittent its hard to capture what might be reproducing it. VRDave_Unity, on the solution you shipped, have you experienced this bug at all? This problem only started occurring for me when I updated to "XR Interaction Toolkit 2.2.0".
     
  18. arashi256

    arashi256

    Joined:
    Oct 11, 2012
    Posts:
    9
    I don't know if it makes a difference, but my XR Interaction Toolkit version is 2.2.1 - yet to come across this bug again using the method I provided. Sorry it didn't work for you! :(
     
    boone0694 likes this.
  19. boone0694

    boone0694

    Joined:
    Jan 7, 2018
    Posts:
    6
    If anyone else is reading this, I found a solution that ended up working. In starter assets in the new XR Toolkit, they included a script called "ActionBasedControllerManager" that tracks actions and enables/disables them based on the control setup intended. They were changing actions I didn't want changed for each control type, so I boiled down the main logic and determined this to work:

    This is for enabling/disabling the actions that are your snapturn, turn, move, and teleport actions.
    Code (CSharp):
    1.     static void EnableAction(InputActionReference actionReference)
    2.     {
    3.         var action = GetInputAction(actionReference);
    4.         if (action != null && !action.enabled)
    5.             action.Enable();
    6.     }
    7.  
    8.     static void DisableAction(InputActionReference actionReference)
    9.     {
    10.         var action = GetInputAction(actionReference);
    11.         if (action != null && action.enabled)
    12.             action.Disable();
    13.     }
    14.  
    15.     static InputAction GetInputAction(InputActionReference actionReference)
    16.     {
    17. #pragma warning disable IDE0031 // Use null propagation -- Do not use for UnityEngine.Object types
    18.         return actionReference != null ? actionReference.action : null;
    19. #pragma warning restore IDE0031
    20.     }

    Then when you want to switch between control types you call them like this:

    Code (CSharp):
    1.  
    2. if(newTurnControl == TurnType.Continuous)
    3.         {
    4.             DisableAction(m_SnapTurn);
    5.             EnableAction(m_Turn);
    6.         }
    7.         else if (newTurnControl == TurnType.Snap)
    8.         {
    9.             DisableAction(m_Turn);
    10.             EnableAction(m_SnapTurn);
    11.         }
     
    arashi256 likes this.
  20. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    620
    I think I like that. Enabling/disabling the action rather than the provider.