using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine.Scripting.APIUpdating;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.UI;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Internal;
namespace UnityEngine.XR.Interaction.Toolkit.Interactors
{
///
/// Behaviour implementation of . An Interaction Group hooks into the interaction system
/// (via ) and enforces that only one within the Group
/// can interact at a time. Each Group member must be either an or an .
///
///
/// The member prioritized for interaction in any given frame is whichever member was interacting the previous frame
/// if it can select in the current frame. If there is no such member, then the interacting member is whichever one
/// in the ordered list of members interacts first.
///
[MovedFrom("UnityEngine.XR.Interaction.Toolkit")]
[DisallowMultipleComponent]
[AddComponentMenu("XR/XR Interaction Group", 11)]
[HelpURL(XRHelpURLConstants.k_XRInteractionGroup)]
[DefaultExecutionOrder(XRInteractionUpdateOrder.k_InteractionGroups)]
public class XRInteractionGroup : MonoBehaviour, IXRInteractionOverrideGroup, IXRGroupMember
{
///
/// These correspond to the default names of the Interaction Groups in the sample XR Rig.
///
public static class GroupNames
{
/// Left controller and hand interactors
public static readonly string k_Left = "Left";
/// Right controller and hand interactors
public static readonly string k_Right = "Right";
/// Head/eye interactors
public static readonly string k_Center = "Center";
}
[Serializable]
internal class GroupMemberAndOverridesPair
{
[RequireInterface(typeof(IXRGroupMember))]
public Object groupMember;
[RequireInterface(typeof(IXRGroupMember))]
public List overrideGroupMembers = new List();
}
///
public event Action registered;
///
public event Action unregistered;
[SerializeField]
[Tooltip("The name of the interaction group, which can be used to retrieve it from the Interaction Manager.")]
string m_GroupName;
///
public string groupName => m_GroupName;
[SerializeField]
[Tooltip("The XR Interaction Manager that this Interaction Group will communicate with (will find one if not set manually).")]
XRInteractionManager m_InteractionManager;
XRInteractionManager m_RegisteredInteractionManager;
///
/// The that this Interaction Group will communicate with (will find one if ).
///
public XRInteractionManager interactionManager
{
get => m_InteractionManager;
set
{
m_InteractionManager = value;
if (Application.isPlaying && isActiveAndEnabled)
RegisterWithInteractionManager();
}
}
///
public IXRInteractionGroup containingGroup { get; private set; }
[SerializeField]
[Tooltip("Ordered list of Interactors or Interaction Groups that are registered with the Group on Awake.")]
[RequireInterface(typeof(IXRGroupMember))]
List m_StartingGroupMembers = new List();
///
/// Ordered list of Interactors or Interaction Groups that are registered with the Group on Awake.
/// All objects in this list should implement the interface and either the
/// interface or the interface.
///
///
/// There are separate methods to access and modify the Group members used after Awake.
///
///
///
///
///
///
///
public List startingGroupMembers
{
get => m_StartingGroupMembers;
set
{
m_StartingGroupMembers = value;
RemoveMissingMembersFromStartingOverridesMap();
}
}
[SerializeField]
[Tooltip("Configuration for each Group Member of which other Members are able to override its interaction " +
"when they attempt to select, despite the difference in priority order.")]
List m_StartingInteractionOverridesMap = new List();
///
public IXRInteractor activeInteractor { get; private set; }
///
public IXRInteractor focusInteractor { get; private set; }
///
public IXRFocusInteractable focusInteractable { get; private set; }
// Used by custom editor to check if we can edit the starting configuration
internal bool isRegisteredWithInteractionManager => m_RegisteredInteractionManager != null;
internal bool hasRegisteredStartingMembers { get; private set; }
readonly RegistrationList m_GroupMembers = new RegistrationList();
readonly List m_TempGroupMembers = new List();
bool m_IsProcessingGroupMembers;
///
/// Mapping of each group member to a set of other members that can override its interaction via selection.
///
readonly Dictionary> m_InteractionOverridesMap =
new Dictionary>();
readonly List m_ValidTargets = new List();
static readonly List s_InteractablesSelected = new List();
static readonly List s_InteractablesHovered = new List();
///
/// See .
///
[Conditional("UNITY_EDITOR")]
protected virtual void Reset()
{
#if UNITY_EDITOR
// Don't need to do anything; method kept for backwards compatibility.
#endif
}
///
/// See .
///
protected virtual void Awake()
{
// Setup Interaction Manager
FindCreateInteractionManager();
// Starting member interactors will be re-registered with the manager below when they are added to the group.
// Make sure the group is registered first.
RegisterWithInteractionManager();
// It is more efficient to add than move, but if there are existing items
// use move to ensure the correct order dictated by the starting lists.
if (m_GroupMembers.flushedCount > 0)
{
var index = 0;
foreach (var obj in m_StartingGroupMembers)
{
if (obj != null && obj is IXRGroupMember groupMember)
MoveGroupMemberTo(groupMember, index++);
}
}
else
{
foreach (var obj in m_StartingGroupMembers)
{
if (obj != null && obj is IXRGroupMember groupMember)
AddGroupMember(groupMember);
}
}
if (string.IsNullOrWhiteSpace(m_GroupName))
m_GroupName = gameObject.name;
RemoveMissingMembersFromStartingOverridesMap();
foreach (var groupMemberAndOverridesPair in m_StartingInteractionOverridesMap)
{
var groupMemberObj = groupMemberAndOverridesPair.groupMember;
if (groupMemberObj == null || !(groupMemberObj is IXRGroupMember groupMember))
continue;
foreach (var overrideGroupMemberObj in groupMemberAndOverridesPair.overrideGroupMembers)
{
if (overrideGroupMemberObj != null && overrideGroupMemberObj is IXRGroupMember overrideGroupMember)
AddInteractionOverrideForGroupMember(groupMember, overrideGroupMember);
}
}
hasRegisteredStartingMembers = true;
}
internal void RemoveMissingMembersFromStartingOverridesMap()
{
for (var i = m_StartingInteractionOverridesMap.Count - 1; i >= 0; i--)
{
var groupMemberAndOverrides = m_StartingInteractionOverridesMap[i];
if (!m_StartingGroupMembers.Contains(groupMemberAndOverrides.groupMember))
{
m_StartingInteractionOverridesMap.RemoveAt(i);
}
else
{
var overrides = groupMemberAndOverrides.overrideGroupMembers;
for (var j = overrides.Count - 1; j >= 0; j--)
{
if (!m_StartingGroupMembers.Contains(overrides[j]))
overrides.RemoveAt(j);
}
}
}
}
///
/// See .
///
protected virtual void OnEnable()
{
FindCreateInteractionManager();
RegisterWithInteractionManager();
}
///
/// See .
///
protected virtual void OnDisable()
{
UnregisterWithInteractionManager();
}
///
/// See .
///
protected virtual void OnDestroy()
{
hasRegisteredStartingMembers = false;
m_InteractionOverridesMap.Clear();
ClearGroupMembers();
}
///
/// Adds to the list of Group members that are to be added as
/// interaction overrides for on Awake. Both objects must already be
/// included in the list. The override object should implement either the
/// interface or the interface.
///
/// The Group member whose interaction can be potentially overridden by
/// .
/// The Group member to add as a possible interaction override.
///
/// Use to add to the interaction overrides used after Awake.
///
///
///
public void AddStartingInteractionOverride(Object sourceGroupMember, Object overrideGroupMember)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return;
}
if (overrideGroupMember == null)
{
Debug.LogError($"{nameof(overrideGroupMember)} cannot be null.");
return;
}
if (!m_StartingGroupMembers.Contains(sourceGroupMember))
{
Debug.LogError($"Cannot add starting override group member for source member {sourceGroupMember} " +
$"because {sourceGroupMember} is not included in the starting group members.", this);
return;
}
if (!m_StartingGroupMembers.Contains(overrideGroupMember))
{
Debug.LogError($"Cannot add override group member {overrideGroupMember} for source member " +
$"because {overrideGroupMember} is not included in the starting group members.", this);
return;
}
if (TryGetStartingGroupMemberAndOverridesPair(sourceGroupMember, out var groupMemberAndOverrides))
{
groupMemberAndOverrides.overrideGroupMembers.Add(overrideGroupMember);
}
else
{
m_StartingInteractionOverridesMap.Add(new GroupMemberAndOverridesPair
{
groupMember = sourceGroupMember,
overrideGroupMembers = new List { overrideGroupMember }
});
}
}
///
/// Removes from the list of Group members that are to be added as
/// interaction overrides for on Awake.
///
/// The Group member whose interaction can no longer be overridden by
/// .
/// The Group member to remove as a possible interaction override.
///
/// Returns if was removed from the list of
/// potential overrides for . Otherwise, returns
/// if was not part of the list.
///
///
/// Use to remove from the interaction overrides used after Awake.
///
///
///
public bool RemoveStartingInteractionOverride(Object sourceGroupMember, Object overrideGroupMember)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return false;
}
return TryGetStartingGroupMemberAndOverridesPair(sourceGroupMember, out var groupMemberAndOverrides) &&
groupMemberAndOverrides.overrideGroupMembers.Remove(overrideGroupMember);
}
bool TryGetStartingGroupMemberAndOverridesPair(Object sourceGroupMember,
out GroupMemberAndOverridesPair groupMemberAndOverrides)
{
if (sourceGroupMember == null)
{
groupMemberAndOverrides = null;
return false;
}
foreach (var pair in m_StartingInteractionOverridesMap)
{
if (pair.groupMember != sourceGroupMember)
continue;
groupMemberAndOverrides = pair;
return true;
}
groupMemberAndOverrides = null;
return false;
}
///
void IXRInteractionGroup.OnRegistered(InteractionGroupRegisteredEventArgs args)
{
if (args.manager != m_InteractionManager)
Debug.LogWarning($"An Interaction Group was registered with an unexpected {nameof(XRInteractionManager)}." +
$" {this} was expecting to communicate with \"{m_InteractionManager}\" but was registered with \"{args.manager}\".", this);
m_RegisteredInteractionManager = args.manager;
m_GroupMembers.Flush();
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
if (groupMember.containingGroup == null)
RegisterAsGroupMember(groupMember);
}
m_IsProcessingGroupMembers = false;
registered?.Invoke(args);
}
///
void IXRInteractionGroup.OnBeforeUnregistered()
{
m_GroupMembers.Flush();
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
RegisterAsNonGroupMember(groupMember);
}
m_IsProcessingGroupMembers = false;
}
///
void IXRInteractionGroup.OnUnregistered(InteractionGroupUnregisteredEventArgs args)
{
if (args.manager != m_RegisteredInteractionManager)
Debug.LogWarning($"An Interaction Group was unregistered from an unexpected {nameof(XRInteractionManager)}." +
$" {this} was expecting to communicate with \"{m_RegisteredInteractionManager}\" but was unregistered from \"{args.manager}\".", this);
m_RegisteredInteractionManager = null;
unregistered?.Invoke(args);
}
///
public void AddGroupMember(IXRGroupMember groupMember)
{
if (groupMember == null)
throw new ArgumentNullException(nameof(groupMember));
if (!ValidateAddGroupMember(groupMember))
return;
if (m_IsProcessingGroupMembers)
Debug.LogWarning($"{groupMember} added while {name} is processing Group members. It won't be processed until the next process.", this);
if (m_GroupMembers.Register(groupMember))
RegisterAsGroupMember(groupMember);
}
///
public void MoveGroupMemberTo(IXRGroupMember groupMember, int newIndex)
{
if (groupMember == null)
throw new ArgumentNullException(nameof(groupMember));
if (!ValidateAddGroupMember(groupMember))
return;
// BaseRegistrationList does not yet support reordering with pending registration changes.
if (m_IsProcessingGroupMembers)
{
Debug.LogError($"Cannot move {groupMember} while {name} is processing Group members.", this);
return;
}
m_GroupMembers.Flush();
if (m_GroupMembers.MoveItemImmediately(groupMember, newIndex) && groupMember.containingGroup == null)
RegisterAsGroupMember(groupMember);
}
bool ValidateAddGroupMember(IXRGroupMember groupMember)
{
if (!(groupMember is IXRInteractor || groupMember is IXRInteractionGroup))
{
Debug.LogError($"Group member {groupMember} must be either an Interactor or an Interaction Group.", this);
return false;
}
if (groupMember.containingGroup != null && !ReferenceEquals(groupMember.containingGroup, this))
{
Debug.LogError($"Cannot add/move {groupMember} because it is already part of a Group. Remove the member from the Group first.", this);
return false;
}
if (groupMember is IXRInteractionGroup subGroup && subGroup.HasDependencyOnGroup(this))
{
Debug.LogError($"Cannot add/move {groupMember} because this would create a circular dependency of groups.", this);
return false;
}
return true;
}
///
public bool RemoveGroupMember(IXRGroupMember groupMember)
{
if (m_GroupMembers.Unregister(groupMember))
{
// Reset active interactor if it was part of the member that was removed
if (activeInteractor != null && GroupMemberIsOrContainsInteractor(groupMember, activeInteractor))
activeInteractor = null;
m_InteractionOverridesMap.Remove(groupMember);
RegisterAsNonGroupMember(groupMember);
return true;
}
return false;
}
bool GroupMemberIsOrContainsInteractor(IXRGroupMember groupMember, IXRInteractor interactor)
{
if (ReferenceEquals(groupMember, interactor))
return true;
if (!(groupMember is IXRInteractionGroup memberGroup))
return false;
memberGroup.GetGroupMembers(m_TempGroupMembers);
foreach (var subGroupMember in m_TempGroupMembers)
{
if (GroupMemberIsOrContainsInteractor(subGroupMember, interactor))
return true;
}
return false;
}
///
public void ClearGroupMembers()
{
m_GroupMembers.Flush();
for (var index = m_GroupMembers.flushedCount - 1; index >= 0; --index)
{
var groupMember = m_GroupMembers.GetRegisteredItemAt(index);
RemoveGroupMember(groupMember);
}
}
///
public bool ContainsGroupMember(IXRGroupMember groupMember)
{
return m_GroupMembers.IsRegistered(groupMember);
}
///
public void GetGroupMembers(List results)
{
if (results == null)
throw new ArgumentNullException(nameof(results));
m_GroupMembers.GetRegisteredItems(results);
}
///
public bool HasDependencyOnGroup(IXRInteractionGroup group)
{
if (ReferenceEquals(group, this))
return true;
GetGroupMembers(m_TempGroupMembers);
foreach (var groupMember in m_TempGroupMembers)
{
if (groupMember is IXRInteractionGroup subGroup && subGroup.HasDependencyOnGroup(group))
return true;
}
return false;
}
///
public void AddInteractionOverrideForGroupMember(IXRGroupMember sourceGroupMember, IXRGroupMember overrideGroupMember)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return;
}
if (overrideGroupMember == null)
{
Debug.LogError($"{nameof(overrideGroupMember)} cannot be null.");
return;
}
if (!(overrideGroupMember is IXRSelectInteractor || overrideGroupMember is IXRInteractionOverrideGroup))
{
Debug.LogError($"Override group member {overrideGroupMember} must implement either " +
$"{nameof(IXRSelectInteractor)} or {nameof(IXRInteractionOverrideGroup)}.", this);
return;
}
if (!ContainsGroupMember(sourceGroupMember))
{
Debug.LogError($"Cannot add override group member for source member {sourceGroupMember} because {sourceGroupMember} " +
"is not registered with the Group. Call AddGroupMember first.", this);
return;
}
if (!ContainsGroupMember(overrideGroupMember))
{
Debug.LogError($"Cannot add override group member {overrideGroupMember} for source member because {overrideGroupMember} " +
"is not registered with the Group. Call AddGroupMember first.", this);
return;
}
if (GroupMemberIsPartOfOverrideChain(overrideGroupMember, sourceGroupMember))
{
Debug.LogError($"Cannot add {overrideGroupMember} as an override group member for {sourceGroupMember} " +
"because this would create a loop of group member overrides.", this);
return;
}
if (m_InteractionOverridesMap.TryGetValue(sourceGroupMember, out var overrides))
overrides.Add(overrideGroupMember);
else
m_InteractionOverridesMap[sourceGroupMember] = new HashSet { overrideGroupMember };
}
///
public bool GroupMemberIsPartOfOverrideChain(IXRGroupMember sourceGroupMember, IXRGroupMember potentialOverrideGroupMember)
{
if (ReferenceEquals(potentialOverrideGroupMember, sourceGroupMember))
return true;
if (!m_InteractionOverridesMap.TryGetValue(sourceGroupMember, out var overrides))
return false;
foreach (var nextOverride in overrides)
{
if (GroupMemberIsPartOfOverrideChain(nextOverride, potentialOverrideGroupMember))
return true;
}
return false;
}
///
public bool RemoveInteractionOverrideForGroupMember(IXRGroupMember sourceGroupMember, IXRGroupMember overrideGroupMember)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return false;
}
if (!ContainsGroupMember(sourceGroupMember))
{
Debug.LogError($"Cannot remove override group member for source member {sourceGroupMember} because {sourceGroupMember} " +
"is not registered with the Group.", this);
return false;
}
return m_InteractionOverridesMap.TryGetValue(sourceGroupMember, out var overrides) && overrides.Remove(overrideGroupMember);
}
///
public bool ClearInteractionOverridesForGroupMember(IXRGroupMember sourceGroupMember)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return false;
}
if (!ContainsGroupMember(sourceGroupMember))
{
Debug.LogError($"Cannot clear override group members for source member {sourceGroupMember} because {sourceGroupMember} " +
"is not registered with the Group.", this);
return false;
}
if (!m_InteractionOverridesMap.TryGetValue(sourceGroupMember, out var overrides))
return false;
overrides.Clear();
return true;
}
///
public void GetInteractionOverridesForGroupMember(IXRGroupMember sourceGroupMember, HashSet results)
{
if (sourceGroupMember == null)
{
Debug.LogError($"{nameof(sourceGroupMember)} cannot be null.");
return;
}
if (results == null)
{
Debug.LogError($"{nameof(results)} cannot be null.");
return;
}
if (!ContainsGroupMember(sourceGroupMember))
{
Debug.LogError($"Cannot get override group members for source member {sourceGroupMember} because {sourceGroupMember} " +
"is not registered with the Group.", this);
return;
}
results.Clear();
if (m_InteractionOverridesMap.TryGetValue(sourceGroupMember, out var overrides))
results.UnionWith(overrides);
}
void FindCreateInteractionManager()
{
if (m_InteractionManager != null)
return;
m_InteractionManager = ComponentLocatorUtility.FindOrCreateComponent();
}
void RegisterWithInteractionManager()
{
if (m_RegisteredInteractionManager == m_InteractionManager)
return;
UnregisterWithInteractionManager();
if (m_InteractionManager != null)
{
m_InteractionManager.RegisterInteractionGroup(this);
}
}
void UnregisterWithInteractionManager()
{
if (m_RegisteredInteractionManager == null)
return;
m_RegisteredInteractionManager.UnregisterInteractionGroup(this);
}
void RegisterAsGroupMember(IXRGroupMember groupMember)
{
if (m_RegisteredInteractionManager == null)
return;
groupMember.OnRegisteringAsGroupMember(this);
ReRegisterGroupMemberWithInteractionManager(groupMember);
}
void RegisterAsNonGroupMember(IXRGroupMember groupMember)
{
if (m_RegisteredInteractionManager == null)
return;
groupMember.OnRegisteringAsNonGroupMember();
ReRegisterGroupMemberWithInteractionManager(groupMember);
}
void ReRegisterGroupMemberWithInteractionManager(IXRGroupMember groupMember)
{
if (m_RegisteredInteractionManager == null)
return;
// Re-register the interactor or group so the manager can update its status as part of a group
switch (groupMember)
{
case IXRInteractor interactor:
if (m_RegisteredInteractionManager.IsRegistered(interactor))
{
m_RegisteredInteractionManager.UnregisterInteractor(interactor);
m_RegisteredInteractionManager.RegisterInteractor(interactor);
}
break;
case IXRInteractionGroup group:
if (m_RegisteredInteractionManager.IsRegistered(group))
{
m_RegisteredInteractionManager.UnregisterInteractionGroup(group);
m_RegisteredInteractionManager.RegisterInteractionGroup(group);
}
break;
default:
Debug.LogError($"Group member {groupMember} must be either an Interactor or an Interaction Group.", this);
break;
}
}
///
void IXRInteractionGroup.PreprocessGroupMembers(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
// Flush once at the start of the update phase, and this is the first method invoked by the manager
m_GroupMembers.Flush();
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
switch (groupMember)
{
case IXRInteractor interactor:
if (!m_RegisteredInteractionManager.IsRegistered(interactor))
continue;
interactor.PreprocessInteractor(updatePhase);
break;
case IXRInteractionGroup group:
if (!m_RegisteredInteractionManager.IsRegistered(group))
continue;
group.PreprocessGroupMembers(updatePhase);
break;
}
}
m_IsProcessingGroupMembers = false;
}
///
void IXRInteractionGroup.ProcessGroupMembers(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
switch (groupMember)
{
case IXRInteractor interactor:
if (!m_RegisteredInteractionManager.IsRegistered(interactor))
continue;
interactor.ProcessInteractor(updatePhase);
break;
case IXRInteractionGroup group:
if (!m_RegisteredInteractionManager.IsRegistered(group))
continue;
group.ProcessGroupMembers(updatePhase);
break;
}
}
m_IsProcessingGroupMembers = false;
}
///
void IXRInteractionGroup.UpdateGroupMemberInteractions()
{
// Prioritize previous active interactor if it can select this frame
IXRInteractor prePrioritizedInteractor = null;
if (activeInteractor != null && m_RegisteredInteractionManager.IsRegistered(activeInteractor) &&
activeInteractor is IXRSelectInteractor activeSelectInteractor &&
CanStartOrContinueAnySelect(activeSelectInteractor))
{
prePrioritizedInteractor = activeInteractor;
}
((IXRInteractionGroup)this).UpdateGroupMemberInteractions(prePrioritizedInteractor, out var interactorThatPerformedInteraction);
activeInteractor = interactorThatPerformedInteraction;
}
bool CanStartOrContinueAnySelect(IXRSelectInteractor selectInteractor)
{
if (selectInteractor.keepSelectedTargetValid)
{
foreach (var interactable in selectInteractor.interactablesSelected)
{
if (m_RegisteredInteractionManager.CanSelect(selectInteractor, interactable))
return true;
}
}
m_RegisteredInteractionManager.GetValidTargets(selectInteractor, m_ValidTargets);
foreach (var target in m_ValidTargets)
{
if (!(target is IXRSelectInteractable selectInteractable))
continue;
if (m_RegisteredInteractionManager.CanSelect(selectInteractor, selectInteractable))
return true;
}
return false;
}
///
void IXRInteractionGroup.UpdateGroupMemberInteractions(IXRInteractor prePrioritizedInteractor, out IXRInteractor interactorThatPerformedInteraction)
{
if (((IXRInteractionOverrideGroup)this).ShouldOverrideActiveInteraction(out var overridingInteractor))
prePrioritizedInteractor = overridingInteractor;
interactorThatPerformedInteraction = null;
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
switch (groupMember)
{
case IXRInteractor interactor:
if (!m_RegisteredInteractionManager.IsRegistered(interactor))
continue;
var preventInteraction = prePrioritizedInteractor != null && interactor != prePrioritizedInteractor;
UpdateInteractorInteractions(interactor, preventInteraction, out var performedInteraction);
if (performedInteraction)
{
interactorThatPerformedInteraction = interactor;
prePrioritizedInteractor = interactor;
}
break;
case IXRInteractionGroup group:
if (!m_RegisteredInteractionManager.IsRegistered(group))
continue;
group.UpdateGroupMemberInteractions(prePrioritizedInteractor, out var interactorInSubGroupThatPerformedInteraction);
if (interactorInSubGroupThatPerformedInteraction != null)
{
interactorThatPerformedInteraction = interactorInSubGroupThatPerformedInteraction;
prePrioritizedInteractor = interactorInSubGroupThatPerformedInteraction;
}
break;
}
}
m_IsProcessingGroupMembers = false;
activeInteractor = interactorThatPerformedInteraction;
}
///
bool IXRInteractionOverrideGroup.ShouldOverrideActiveInteraction(out IXRSelectInteractor overridingInteractor)
{
overridingInteractor = null;
if (activeInteractor == null ||
!TryGetOverridesForContainedInteractor(activeInteractor, out var activeMemberOverrides))
{
return false;
}
// Iterate through group members rather than the overrides set so we can ensure that priority is respected
var shouldOverride = false;
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember) || !activeMemberOverrides.Contains(groupMember))
continue;
if (ShouldGroupMemberOverrideInteraction(activeInteractor, groupMember, out overridingInteractor))
{
shouldOverride = true;
break;
}
}
m_IsProcessingGroupMembers = false;
return shouldOverride;
}
///
/// Tries to find the set of overrides for or overrides for the member Group that
/// contains if is nested.
///
/// The contained interactor to check against.
/// The set of override Group members for or
/// overrides for the member Group that contains .
///
/// Returns if has overrides or a member Group
/// containing has overrides, otherwise.
///
bool TryGetOverridesForContainedInteractor(IXRInteractor interactor, out HashSet overrideGroupMembers)
{
overrideGroupMembers = null;
if (!(interactor is IXRGroupMember interactorAsGroupMember))
{
Debug.LogError($"Interactor {interactor} must be a {nameof(IXRGroupMember)}.", this);
return false;
}
// If the interactor is nested, bubble up to find the top-level member Group that contains the interactor.
var nextContainingGroup = interactorAsGroupMember.containingGroup;
var groupMemberForInteractor = interactorAsGroupMember;
while (nextContainingGroup != null && !ReferenceEquals(nextContainingGroup, this))
{
if (nextContainingGroup is IXRGroupMember groupMemberGroup)
{
nextContainingGroup = groupMemberGroup.containingGroup;
groupMemberForInteractor = groupMemberGroup;
}
else
{
nextContainingGroup = null;
}
}
if (nextContainingGroup == null)
{
Debug.LogError($"Interactor {interactor} must be contained by this group or one of its sub-groups.", this);
return false;
}
return m_InteractionOverridesMap.TryGetValue(groupMemberForInteractor, out overrideGroupMembers);
}
///
bool IXRInteractionOverrideGroup.ShouldAnyMemberOverrideInteraction(IXRInteractor interactingInteractor,
out IXRSelectInteractor overridingInteractor)
{
overridingInteractor = null;
var shouldOverride = false;
m_IsProcessingGroupMembers = true;
foreach (var groupMember in m_GroupMembers.registeredSnapshot)
{
if (!m_GroupMembers.IsStillRegistered(groupMember))
continue;
if (ShouldGroupMemberOverrideInteraction(interactingInteractor, groupMember, out overridingInteractor))
{
shouldOverride = true;
break;
}
}
m_IsProcessingGroupMembers = false;
return shouldOverride;
}
bool ShouldGroupMemberOverrideInteraction(IXRInteractor interactingInteractor,
IXRGroupMember overrideGroupMember, out IXRSelectInteractor overridingInteractor)
{
overridingInteractor = null;
switch (overrideGroupMember)
{
case IXRSelectInteractor interactor:
if (!m_RegisteredInteractionManager.IsRegistered(interactor))
return false;
if (ShouldInteractorOverrideInteraction(interactingInteractor, interactor))
{
overridingInteractor = interactor;
return true;
}
break;
case IXRInteractionOverrideGroup group:
if (!m_RegisteredInteractionManager.IsRegistered(group))
return false;
if (group.ShouldAnyMemberOverrideInteraction(interactingInteractor, out overridingInteractor))
return true;
break;
}
return false;
}
///
/// Checks if the given should override the active interaction of
/// - that is, whether can
/// select any interactable that is interacting with.
///
/// The interactor that is currently interacting with at least one interactable.
/// The interactor that is capable of overriding the interaction of .
/// True if should override the active interaction of
/// , false otherwise.
bool ShouldInteractorOverrideInteraction(IXRInteractor interactingInteractor, IXRSelectInteractor overridingInteractor)
{
var interactingSelectInteractor = interactingInteractor as IXRSelectInteractor;
var interactingHoverInteractor = interactingInteractor as IXRHoverInteractor;
m_RegisteredInteractionManager.GetValidTargets(overridingInteractor, m_ValidTargets);
foreach (var target in m_ValidTargets)
{
if (!(target is IXRSelectInteractable selectInteractable) ||
!m_RegisteredInteractionManager.CanSelect(overridingInteractor, selectInteractable))
{
continue;
}
if (interactingSelectInteractor != null && interactingSelectInteractor.IsSelecting(selectInteractable))
return true;
if (interactingHoverInteractor != null && target is IXRHoverInteractable hoverInteractable &&
interactingHoverInteractor.IsHovering(hoverInteractable))
{
return true;
}
}
return false;
}
void UpdateInteractorInteractions(IXRInteractor interactor, bool preventInteraction, out bool performedInteraction)
{
performedInteraction = false;
using (XRInteractionManager.s_GetValidTargetsMarker.Auto())
m_RegisteredInteractionManager.GetValidTargets(interactor, m_ValidTargets);
var selectInteractor = interactor as IXRSelectInteractor;
var hoverInteractor = interactor as IXRHoverInteractor;
if (selectInteractor != null)
{
using (XRInteractionManager.s_EvaluateInvalidSelectionsMarker.Auto())
{
if (preventInteraction)
ClearAllInteractorSelections(selectInteractor);
else
m_RegisteredInteractionManager.ClearInteractorSelection(selectInteractor, m_ValidTargets);
}
}
if (hoverInteractor != null)
{
using (XRInteractionManager.s_EvaluateInvalidHoversMarker.Auto())
{
if (preventInteraction)
ClearAllInteractorHovers(hoverInteractor);
else
m_RegisteredInteractionManager.ClearInteractorHover(hoverInteractor, m_ValidTargets);
}
}
if (preventInteraction)
return;
if (selectInteractor != null)
{
using (XRInteractionManager.s_EvaluateValidSelectionsMarker.Auto())
m_RegisteredInteractionManager.InteractorSelectValidTargets(selectInteractor, m_ValidTargets);
// Alternatively check if the interactor is poke interacting with UGUI
if (selectInteractor.hasSelection || (interactor is IUIInteractor uiInteractor && TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(uiInteractor)))
performedInteraction = true;
}
if (hoverInteractor != null)
{
using (XRInteractionManager.s_EvaluateValidHoversMarker.Auto())
m_RegisteredInteractionManager.InteractorHoverValidTargets(hoverInteractor, m_ValidTargets);
if (hoverInteractor.hasHover)
performedInteraction = true;
}
}
void ClearAllInteractorSelections(IXRSelectInteractor selectInteractor)
{
if (selectInteractor.interactablesSelected.Count == 0)
return;
s_InteractablesSelected.Clear();
s_InteractablesSelected.AddRange(selectInteractor.interactablesSelected);
for (var i = s_InteractablesSelected.Count - 1; i >= 0; --i)
{
var interactable = s_InteractablesSelected[i];
m_RegisteredInteractionManager.SelectExit(selectInteractor, interactable);
}
}
void ClearAllInteractorHovers(IXRHoverInteractor hoverInteractor)
{
if (hoverInteractor.interactablesHovered.Count == 0)
return;
s_InteractablesHovered.Clear();
s_InteractablesHovered.AddRange(hoverInteractor.interactablesHovered);
for (var i = s_InteractablesHovered.Count - 1; i >= 0; --i)
{
var interactable = s_InteractablesHovered[i];
m_RegisteredInteractionManager.HoverExit(hoverInteractor, interactable);
}
}
///
public void OnFocusEntering(FocusEnterEventArgs args)
{
focusInteractable = args.interactableObject;
focusInteractor = args.interactorObject;
}
///
public void OnFocusExiting(FocusExitEventArgs args)
{
if (focusInteractable == args.interactableObject)
{
focusInteractable = null;
focusInteractor = null;
}
}
///
void IXRGroupMember.OnRegisteringAsGroupMember(IXRInteractionGroup group)
{
if (containingGroup != null)
{
Debug.LogError($"{name} is already part of a Group. Remove the member from the Group first.", this);
return;
}
if (!group.ContainsGroupMember(this))
{
Debug.LogError($"{nameof(IXRGroupMember.OnRegisteringAsGroupMember)} was called but the Group does not contain {name}. " +
"Add the member to the Group rather than calling this method directly.", this);
return;
}
containingGroup = group;
}
///
void IXRGroupMember.OnRegisteringAsNonGroupMember()
{
containingGroup = null;
}
}
}