262 lines
9.4 KiB
C#
262 lines
9.4 KiB
C#
using System;
|
|
using Unity.XR.CoreUtils.Bindings.Variables;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.XR.Interaction.Toolkit.Inputs;
|
|
#if XR_HANDS_1_1_OR_NEWER
|
|
using UnityEngine.XR.Hands;
|
|
#endif
|
|
|
|
namespace UnityEngine.XR.Interaction.Toolkit.Samples.Hands
|
|
{
|
|
/// <summary>
|
|
/// Behavior that provides events for when the system gesture starts and ends and when the
|
|
/// menu palm pinch gesture occurs while hand tracking is in use.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see href="https://docs.unity3d.com/Packages/com.unity.xr.hands@1.1/manual/features/metahandtrackingaim.html">Meta Hand Tracking Aim</see>.
|
|
/// </remarks>
|
|
/// <seealso cref="MetaAimHand"/>
|
|
public class MetaSystemGestureDetector : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// The state of the system gesture.
|
|
/// </summary>
|
|
/// <seealso cref="systemGestureState"/>
|
|
public enum SystemGestureState
|
|
{
|
|
/// <summary>
|
|
/// The system gesture has fully ended.
|
|
/// </summary>
|
|
Ended,
|
|
|
|
/// <summary>
|
|
/// The system gesture has started or is ongoing. Typically, this means the user is looking at
|
|
/// their palm at eye level or has not yet released the palm pinch gesture or turned their hand around.
|
|
/// </summary>
|
|
Started,
|
|
}
|
|
|
|
[SerializeField]
|
|
InputActionProperty m_AimFlagsAction = new InputActionProperty(new InputAction(expectedControlType: "Integer"));
|
|
|
|
/// <summary>
|
|
/// The Input System action to read the Aim Flags.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Typically a <b>Value</b> action type with an <b>Integer</b> control type with a binding to either:
|
|
/// <list type="bullet">
|
|
/// <item>
|
|
/// <description><c><MetaAimHand>{LeftHand}/aimFlags</c></description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description><c><MetaAimHand>{RightHand}/aimFlags</c></description>
|
|
/// </item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public InputActionProperty aimFlagsAction
|
|
{
|
|
get => m_AimFlagsAction;
|
|
set
|
|
{
|
|
if (Application.isPlaying)
|
|
UnbindAimFlags();
|
|
|
|
m_AimFlagsAction = value;
|
|
|
|
if (Application.isPlaying && isActiveAndEnabled)
|
|
BindAimFlags();
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
UnityEvent m_SystemGestureStarted;
|
|
|
|
/// <summary>
|
|
/// Calls the methods in its invocation list when the system gesture starts, which typically occurs when
|
|
/// the user looks at their palm at eye level.
|
|
/// </summary>
|
|
/// <seealso cref="systemGestureEnded"/>
|
|
/// <seealso cref="MetaAimFlags.SystemGesture"/>
|
|
public UnityEvent systemGestureStarted
|
|
{
|
|
get => m_SystemGestureStarted;
|
|
set => m_SystemGestureStarted = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
UnityEvent m_SystemGestureEnded;
|
|
|
|
/// <summary>
|
|
/// Calls the methods in its invocation list when the system gesture ends.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This behavior postpones ending the system gesture until the user has turned their hand around.
|
|
/// In other words, it isn't purely based on the <see cref="MetaAimFlags.SystemGesture"/>
|
|
/// being cleared from the aim flags in order to better replicate the native visual feedback in the Meta Home menu.
|
|
/// </remarks>
|
|
/// <seealso cref="systemGestureStarted"/>
|
|
/// <seealso cref="MetaAimFlags.SystemGesture"/>
|
|
public UnityEvent systemGestureEnded
|
|
{
|
|
get => m_SystemGestureEnded;
|
|
set => m_SystemGestureEnded = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
UnityEvent m_MenuPressed;
|
|
|
|
/// <summary>
|
|
/// Calls the methods in its invocation list when the menu button is triggered by a palm pinch gesture.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is triggered by the non-dominant hand, which is the one with the menu icon (☰).
|
|
/// The universal menu (Oculus icon) on the dominant hand does not trigger this event.
|
|
/// </remarks>
|
|
/// <seealso cref="MetaAimFlags.MenuPressed"/>
|
|
public UnityEvent menuPressed
|
|
{
|
|
get => m_MenuPressed;
|
|
set => m_MenuPressed = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The state of the system gesture.
|
|
/// </summary>
|
|
/// <seealso cref="SystemGestureState"/>
|
|
/// <seealso cref="systemGestureStarted"/>
|
|
/// <seealso cref="systemGestureEnded"/>
|
|
public IReadOnlyBindableVariable<SystemGestureState> systemGestureState => m_SystemGestureState;
|
|
|
|
readonly BindableEnum<SystemGestureState> m_SystemGestureState = new BindableEnum<SystemGestureState>(checkEquality: false);
|
|
|
|
#if XR_HANDS_1_1_OR_NEWER
|
|
[NonSerialized] // NonSerialized is required to avoid an "Unsupported enum base type" error about the Flags enum being ulong
|
|
MetaAimFlags m_AimFlags;
|
|
#endif
|
|
|
|
bool m_AimFlagsBound;
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected void OnEnable()
|
|
{
|
|
BindAimFlags();
|
|
|
|
#if XR_HANDS_1_1_OR_NEWER
|
|
var action = m_AimFlagsAction.action;
|
|
if (action != null)
|
|
// Force invoking the events upon initialization to simplify making sure the callback's desired results are synced
|
|
UpdateAimFlags((MetaAimFlags)action.ReadValue<int>(), true);
|
|
#else
|
|
Debug.LogWarning("Script requires XR Hands (com.unity.xr.hands) package to monitor Meta Aim Flags. Install using Window > Package Manager or click Fix on the related issue in Edit > Project Settings > XR Plug-in Management > Project Validation.", this);
|
|
SetGestureState(SystemGestureState.Ended, true);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected void OnDisable()
|
|
{
|
|
UnbindAimFlags();
|
|
}
|
|
|
|
void BindAimFlags()
|
|
{
|
|
if (m_AimFlagsBound)
|
|
return;
|
|
|
|
var action = m_AimFlagsAction.action;
|
|
if (action == null)
|
|
return;
|
|
|
|
action.performed += OnAimFlagsActionPerformedOrCanceled;
|
|
action.canceled += OnAimFlagsActionPerformedOrCanceled;
|
|
m_AimFlagsBound = true;
|
|
|
|
m_AimFlagsAction.EnableDirectAction();
|
|
}
|
|
|
|
void UnbindAimFlags()
|
|
{
|
|
if (!m_AimFlagsBound)
|
|
return;
|
|
|
|
var action = m_AimFlagsAction.action;
|
|
if (action == null)
|
|
return;
|
|
|
|
m_AimFlagsAction.DisableDirectAction();
|
|
|
|
action.performed -= OnAimFlagsActionPerformedOrCanceled;
|
|
action.canceled -= OnAimFlagsActionPerformedOrCanceled;
|
|
m_AimFlagsBound = false;
|
|
}
|
|
|
|
void SetGestureState(SystemGestureState state, bool forceInvoke)
|
|
{
|
|
if (!forceInvoke && m_SystemGestureState.Value == state)
|
|
return;
|
|
|
|
m_SystemGestureState.Value = state;
|
|
switch (state)
|
|
{
|
|
case SystemGestureState.Ended:
|
|
m_SystemGestureEnded?.Invoke();
|
|
break;
|
|
case SystemGestureState.Started:
|
|
m_SystemGestureStarted?.Invoke();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if XR_HANDS_1_1_OR_NEWER
|
|
void UpdateAimFlags(MetaAimFlags value, bool forceInvoke = false)
|
|
{
|
|
var hadMenuPressed = (m_AimFlags & MetaAimFlags.MenuPressed) != 0;
|
|
m_AimFlags = value;
|
|
var hasSystemGesture = (m_AimFlags & MetaAimFlags.SystemGesture) != 0;
|
|
var hasMenuPressed = (m_AimFlags & MetaAimFlags.MenuPressed) != 0;
|
|
var hasValid = (m_AimFlags & MetaAimFlags.Valid) != 0;
|
|
var hasIndexPinching = (m_AimFlags & MetaAimFlags.IndexPinching) != 0;
|
|
|
|
if (!hadMenuPressed && hasMenuPressed)
|
|
{
|
|
m_MenuPressed?.Invoke();
|
|
}
|
|
|
|
if (hasSystemGesture || hasMenuPressed)
|
|
{
|
|
SetGestureState(SystemGestureState.Started, forceInvoke);
|
|
return;
|
|
}
|
|
|
|
if (hasValid)
|
|
{
|
|
SetGestureState(SystemGestureState.Ended, forceInvoke);
|
|
return;
|
|
}
|
|
|
|
// We want to keep the system gesture going when the user is still index pinching
|
|
// even though the SystemGesture flag is no longer set.
|
|
if (hasIndexPinching && m_SystemGestureState.Value != SystemGestureState.Ended)
|
|
{
|
|
SetGestureState(SystemGestureState.Started, forceInvoke);
|
|
return;
|
|
}
|
|
|
|
SetGestureState(SystemGestureState.Ended, forceInvoke);
|
|
}
|
|
#endif
|
|
|
|
void OnAimFlagsActionPerformedOrCanceled(InputAction.CallbackContext context)
|
|
{
|
|
#if XR_HANDS_1_1_OR_NEWER
|
|
UpdateAimFlags((MetaAimFlags)context.ReadValue<int>());
|
|
#endif
|
|
}
|
|
}
|
|
}
|