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.
}
}
}