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.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
/// <summary>
/// Interactable that acts as an entry point for passing interactions through to other interactables.
/// <para>
/// This interactable is used to work around the limitation where the <see cref="XRInteractionManager"/> only
/// maps an object's colliders to a single <see cref="XRBaseInteractable"/>. This limitation prevents an object
/// from having multiple <see cref="XRBaseInteractable"/>s on it. By using this interactable, interactions can be
/// passed through to other interactables on the object.
/// </para>
/// </summary>
public class MultiInteractable : XRBaseInteractable
{
[SerializeField]
private List<XRBaseInteractable> interactables;
private readonly List<XRBaseInteractable> hoverTargetList = new List<XRBaseInteractable>();
/// <inheritdoc/>
protected override void Awake()
{
base.Awake();
// calculate the layer mask for this interactable by using the layer masks of the child interactables
interactionLayerMask = 0;
foreach (XRBaseInteractable interactable in interactables)
{
if (interactionLayerMask == 0)
{
interactionLayerMask = interactable.interactionLayerMask;
}
else
{
interactionLayerMask = interactionLayerMask | interactable.interactionLayerMask;
}
}
}
// Activate shouldn't need to be handled here since it requires being in the Select state. The child
// interactable will already be selected and should be able to handle activation directly without any
// interference from a class like this.
/// <inheritdoc/>
protected override void OnHoverEntered(XRBaseInteractor interactor)
{
base.OnHoverEntered(interactor);
foreach (XRBaseInteractable interactable in interactables)
{
interactor.GetHoverTargets(hoverTargetList);
if (interactor.CanHover(interactable) && interactable.IsHoverableBy(interactor) &&
!hoverTargetList.Contains(interactable))
{
interactor.interactionManager.HoverEnter(interactor, interactable);
}
}
}
/// <inheritdoc/>
protected override void OnHoverExited(XRBaseInteractor interactor)
{
base.OnHoverExited(interactor);
foreach (XRBaseInteractable interactable in interactables)
{
interactor.GetHoverTargets(hoverTargetList);
foreach (var target in hoverTargetList)
{
if (!interactor.isHoverActive || !interactor.CanHover(target) || !target.IsHoverableBy(interactor))
{
interactor.interactionManager.HoverExit(interactor, target);
}
}
}
}
/// <inheritdoc/>
protected override void OnSelectEntered(XRBaseInteractor interactor)
{
base.OnSelectEntered(interactor);
// XRBaseInteractor.CanSelect will always return false if the interactor already has something selected.
// Immediately exit selection for this entry point and allow for the other interactables to be selected.
interactor.interactionManager.SelectExit(interactor, this);
foreach (XRBaseInteractable interactable in interactables)
{
if (interactor.CanSelect(interactable) && interactable.IsSelectableBy(interactor))
{
interactor.interactionManager.SelectEnter(interactor, interactable);
}
}
}
}
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.