Search Unity

Question Object With Multiple Interactables

Discussion in 'XR Interaction Toolkit and Input' started by jdh5259, Mar 12, 2021.

  1. jdh5259

    jdh5259

    Joined:
    Sep 14, 2017
    Posts:
    20
    I have been playing around with the XR Interaction Toolkit and wanted to try adding multiple different interactables to an object but it didn't work as I expected it would. I noticed that the interaction manager only maintains a mapping of an object's colliders to a single interactable. This limitation means that, for an object, only the first interactable to register itself with the interaction manager will be handled. It seems to me that adding multiple interactables to an object would be a common use case.

    Due to the single interactable limitation, I tried creating an interactable to act as entry point that would then pass through interactions to other interactables. It seems to work alright for pretty basic stuff but I haven't tested it extensively. I am not really a fan of this approach and would like to see what some alternatives are.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.XR.Interaction.Toolkit;
    4.  
    5. /// <summary>
    6. /// Interactable that acts as an entry point for passing interactions through to other interactables.
    7. /// <para>
    8. /// This interactable is used to work around the limitation where the <see cref="XRInteractionManager"/> only
    9. /// maps an object's colliders to a single <see cref="XRBaseInteractable"/>. This limitation prevents an object
    10. /// from having multiple <see cref="XRBaseInteractable"/>s on it. By using this interactable, interactions can be
    11. /// passed through to other interactables on the object.
    12. /// </para>
    13. /// </summary>
    14. public class MultiInteractable : XRBaseInteractable
    15. {
    16.     [SerializeField]
    17.     private List<XRBaseInteractable> interactables;
    18.  
    19.     private readonly List<XRBaseInteractable> hoverTargetList = new List<XRBaseInteractable>();
    20.  
    21.     /// <inheritdoc/>
    22.     protected override void Awake()
    23.     {
    24.         base.Awake();
    25.  
    26.         // calculate the layer mask for this interactable by using the layer masks of the child interactables
    27.         interactionLayerMask = 0;
    28.         foreach (XRBaseInteractable interactable in interactables)
    29.         {
    30.             if (interactionLayerMask == 0)
    31.             {
    32.                 interactionLayerMask = interactable.interactionLayerMask;
    33.             }
    34.             else
    35.             {
    36.                 interactionLayerMask = interactionLayerMask | interactable.interactionLayerMask;
    37.             }
    38.         }
    39.     }
    40.  
    41.     // Activate shouldn't need to be handled here since it requires being in the Select state. The child
    42.     // interactable will already be selected and should be able to handle activation directly without any
    43.     // interference from a class like this.
    44.  
    45.     /// <inheritdoc/>
    46.     protected override void OnHoverEntered(XRBaseInteractor interactor)
    47.     {
    48.         base.OnHoverEntered(interactor);
    49.  
    50.         foreach (XRBaseInteractable interactable in interactables)
    51.         {
    52.             interactor.GetHoverTargets(hoverTargetList);
    53.             if (interactor.CanHover(interactable) && interactable.IsHoverableBy(interactor) &&
    54.                 !hoverTargetList.Contains(interactable))
    55.             {
    56.                 interactor.interactionManager.HoverEnter(interactor, interactable);
    57.             }
    58.         }
    59.     }
    60.  
    61.     /// <inheritdoc/>
    62.     protected override void OnHoverExited(XRBaseInteractor interactor)
    63.     {
    64.         base.OnHoverExited(interactor);
    65.  
    66.         foreach (XRBaseInteractable interactable in interactables)
    67.         {
    68.             interactor.GetHoverTargets(hoverTargetList);
    69.             foreach (var target in hoverTargetList)
    70.             {
    71.                 if (!interactor.isHoverActive || !interactor.CanHover(target) || !target.IsHoverableBy(interactor))
    72.                 {
    73.                     interactor.interactionManager.HoverExit(interactor, target);
    74.                 }
    75.             }
    76.         }
    77.     }
    78.  
    79.     /// <inheritdoc/>
    80.     protected override void OnSelectEntered(XRBaseInteractor interactor)
    81.     {
    82.         base.OnSelectEntered(interactor);
    83.  
    84.         // XRBaseInteractor.CanSelect will always return false if the interactor already has something selected.
    85.         // Immediately exit selection for this entry point and allow for the other interactables to be selected.
    86.         interactor.interactionManager.SelectExit(interactor, this);
    87.  
    88.         foreach (XRBaseInteractable interactable in interactables)
    89.         {
    90.             if (interactor.CanSelect(interactable) && interactable.IsSelectableBy(interactor))
    91.             {
    92.                 interactor.interactionManager.SelectEnter(interactor, interactable);
    93.             }
    94.         }
    95.     }
    96. }
    Is there an approach that people are using to get around this limitation? Am I missing something or am I going about this all wrong?

    Thanks for any help you can provide.
     
  2. jdh5259

    jdh5259

    Joined:
    Sep 14, 2017
    Posts:
    20
    For anyone finding this later, be aware that the attempted fix above didn't actually work out as well as it seemed to at first. There is a lot of fighting with the collider map and obtaining valid targets from the interactors.

    Still keeping my fingers crossed that someday a single collider can be mapped to multiple interactables.
     
  3. Deleted User

    Deleted User

    Guest

    Hey, hope you're still there... I'm currently facing the same problem. Did you find any workaround?
     
  4. jdh5259

    jdh5259

    Joined:
    Sep 14, 2017
    Posts:
    20
    I had a response typed out for this the other day but apparently forgot to post it... my bad.

    We weren't able to find any suitable workaround and have resorted to structuring our stuff to limit interactable implementations to things like the simple interactable (and reacting to events) and the grab interactable.

    An attempt was made to use multiple interaction managers, one per interactable sharing the same collider. However, trying to manage references to specific interaction managers and ensuring interactables/interactors were using the appropriate one for the desired interaction proved to be very difficult and wasn't really maintainable.
     
  5. Liam-C

    Liam-C

    Joined:
    Mar 6, 2017
    Posts:
    18
    I'm doing basically what you've described and it works well. I have an InteractionManager which is a child class of the built-in XRInteractionManager. There's an InteractionManagerType enum (e.g, Magnet, Grab, UI), and each InteractionManager, Interactable and Interactor specifies an InteractionManagerType. Then, instead of registering to the first XRInteractionManager instance, Interactors/Interactables register to the manager with the matching type. The InteractionManager just has a static Dictionary<InteractionManagerType, InteractionManager> to easily find the right manager.

    This means an object can have a MagnetInteractable and GrabInteractable component on the same object, sharing the same collider, and it works fine because each XRInteractionManager has it's own collider-to-interactable map.
     
  6. matty337s

    matty337s

    Joined:
    Apr 12, 2022
    Posts:
    4
    That sounds exactly what I'm working on too! Would you mind sharing some scripts or snippets of scripts to help myself and others in the future? Much appreciated!
     
  7. DevTom81

    DevTom81

    Joined:
    Aug 10, 2021
    Posts:
    11
    Hey, I have the same problem. I want to be able to grab objects and use them as a teleportation area.