using System; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine.Assertions; using UnityEngine.InputSystem; using UnityEngine.Scripting; namespace UnityEngine.XR.Interaction.Toolkit.Inputs.Interactions { /// /// Interaction that performs the action if the user pushes the control into a slice of a circle along cardinal directions, /// with a deadzone center magnitude. Typically used to define actions for North, South, East, or West for a thumbstick. /// /// /// The interaction performs the action if the control crosses the center threshold defined by /// and it is in a direction matching . Whether the action cancels or performs when leaving or /// re-entering each direction sector depends on the configured . Once the control returns /// to center as defined by the center threshold, the action cancels. /// #if UNITY_EDITOR [InitializeOnLoad] #endif [Preserve] public class SectorInteraction : IInputInteraction { /// /// Sets which cardinal directions to use when determining valid directions to perform the action. /// /// [Flags] public enum Directions { /// /// Do not include any direction, e.g. the center deadzone region of a thumbstick. /// The action will never perform. /// None = 0, /// /// Include North direction, e.g. forward on a thumbstick. /// North = 1 << 0, /// /// Include South direction, e.g. back on a thumbstick. /// South = 1 << 1, /// /// Include East direction, e.g. right on a thumbstick. /// East = 1 << 2, /// /// Include West direction, e.g. left on a thumbstick. /// West = 1 << 3, } /// /// Sets which strategy to use when sweeping the stick around the cardinal directions /// without returning to center for whether the action should perform or cancel. /// /// public enum SweepBehavior { /// /// Perform if initially actuated in a configured valid direction, and will remain so /// (i.e. even if no longer actuating in a valid direction) /// until returning to center, at which point it will cancel. /// Locked, /// /// Perform if initially actuated in a configured valid direction. Cancels when /// no longer actuating in a valid direction, and performs again when re-entering a valid sector /// even when not returning to center. /// [InspectorName("Allow Reentry")] AllowReentry, /// /// Perform if initially actuated in a configured valid direction. Cancels when /// no longer actuating in a valid direction, and remains so when re-entering a valid sector /// without returning to center. /// [InspectorName("Disallow Reentry")] DisallowReentry, /// /// Perform if actuated in a configured valid direction, no matter the initial actuation direction. /// Cancels when not actuated in a valid direction. /// [InspectorName("History Independent")] HistoryIndependent, } /// /// Sets which state this is in. /// /// enum State { /// /// Input control is in the center deadzone region. /// Centered, /// /// The initial latched direction was one of the configured valid directions. /// StartedValidDirection, /// /// The initial latched direction was not one of the configured valid directions. /// StartedInvalidDirection, } /// /// Specifies cardinal direction(s) that, if the user moves the control in /// any of those directions enough to cross the press threshold, the action /// should be performed. /// public Directions directions; /// /// Determines when the action should perform or cancel when sweeping the stick around /// the cardinal directions without returning to center. /// public SweepBehavior sweepBehavior; /// /// Magnitude threshold that an actuated control must cross for the control to /// be considered pressed. /// /// /// If this is less than or equal to 0 (the default), is used instead. /// /// public float pressPoint = -1f; internal float pressPointOrDefault => pressPoint >= 0f ? pressPoint : defaultPressPoint; /// /// The default magnitude threshold that an actuated control must cross for the control to /// be considered pressed. /// public static float defaultPressPoint { get; set; } = 0.5f; State m_State; bool m_WasValidDirection; /// /// See /// /// Context for an interaction occurring that the input system passed here for interaction operations. public void Process(ref InputInteractionContext context) { var isActuated = context.ControlIsActuated(pressPointOrDefault); if (!isActuated) { switch (m_State) { case State.Centered: return; case State.StartedInvalidDirection: case State.StartedValidDirection: m_State = State.Centered; context.Canceled(); return; default: Assert.IsTrue(false, $"Unhandled {nameof(State)}={m_State}"); return; } } var isValidDirection = IsValidDirection(ref context); if (m_State == State.Centered) { m_State = isValidDirection ? State.StartedValidDirection : State.StartedInvalidDirection; if (isValidDirection) { context.PerformedAndStayPerformed(); } m_WasValidDirection = isValidDirection; return; } switch (sweepBehavior) { case SweepBehavior.Locked: break; case SweepBehavior.AllowReentry: if (m_WasValidDirection && !isValidDirection && m_State == State.StartedValidDirection) { context.Canceled(); } else if (!m_WasValidDirection && isValidDirection && m_State == State.StartedValidDirection) { context.PerformedAndStayPerformed(); } break; case SweepBehavior.DisallowReentry: if (m_WasValidDirection && !isValidDirection && m_State == State.StartedValidDirection) { context.Canceled(); } break; case SweepBehavior.HistoryIndependent: if (m_WasValidDirection && !isValidDirection) { context.Canceled(); } else if (!m_WasValidDirection && isValidDirection) { context.PerformedAndStayPerformed(); } break; default: Assert.IsTrue(false, $"Unhandled {nameof(SweepBehavior)}={sweepBehavior}"); break; } m_WasValidDirection = isValidDirection; } /// /// Determines whether the control is pointing towards the configured North, South, East, or West direction(s). /// /// The input control information context. /// Returns if pointing in "this" direction. Otherwise, returns . bool IsValidDirection(ref InputInteractionContext context) { var value = context.ReadValue(); var cardinal = CardinalUtility.GetNearestCardinal(value); var nearestDirection = GetNearestDirection(cardinal); return (nearestDirection & directions) != 0; } static Directions GetNearestDirection(Cardinal value) { switch (value) { case Cardinal.North: return Directions.North; case Cardinal.South: return Directions.South; case Cardinal.East: return Directions.East; case Cardinal.West: return Directions.West; default: Assert.IsTrue(false, $"Unhandled {nameof(Cardinal)}={value}"); return Directions.None; } } /// /// See [IInputInteraction.Reset](xref:UnityEngine.InputSystem.IInputInteraction.Reset). /// public void Reset() { // Do not reset, only do so when no longer actuating. // We cancel when the stick moves outside the sector, but the latched sector // needs to still be set to achieve the desired sweep behavior, so it can't // be reset to default. } [Preserve] static SectorInteraction() { InputSystem.InputSystem.RegisterInteraction(); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad), Preserve] #pragma warning disable IDE0051 // Remove unused private members static void Initialize() #pragma warning restore IDE0051 // Remove unused private members { // Will execute the static constructor as a side effect. } } }