using System; using UnityEngine.InputSystem; using UnityEngine.XR.Interaction.Toolkit.Utilities; using UnityEngine.XR.Interaction.Toolkit.Utilities.Internal; namespace UnityEngine.XR.Interaction.Toolkit.Inputs.Readers { /// /// Interface which allows for callers to read the button's state from an input source. /// /// public interface IXRInputButtonReader : IXRInputValueReader { /// /// Read whether the button is currently performed, which typically means whether the button is being pressed. /// This is typically true for multiple frames. /// /// Returns if the button is performed. Otherwise, returns . /// /// For input actions, this depends directly on the interaction(s) driving the action (including the /// default interaction if no specific interaction has been added to the action or binding). /// bool ReadIsPerformed(); /// /// Read whether the button performed this frame, which typically means whether the button started being pressed during this frame. /// This is typically only true for one single frame. /// /// Returns if the button performed this frame. Otherwise, returns . /// /// For input actions, this depends directly on the interaction(s) driving the action (including the /// default interaction if no specific interaction has been added to the action or binding). /// bool ReadWasPerformedThisFrame(); /// /// Read whether the button completed this frame, which typically means whether the button stopped being pressed during this frame. /// This is typically only true for one single frame. /// /// Returns if the button completed this frame. Otherwise, returns . /// /// For input actions, this depends directly on the interaction(s) driving the action (including the /// default interaction if no specific interaction has been added to the action or binding). /// bool ReadWasCompletedThisFrame(); } /// /// Serializable typed input button reader that can read the current button's state from an input source. /// Behaviors can declare a field of this type to allow them to read input from an input action or any other source. /// [Serializable] public class XRInputButtonReader : IXRInputButtonReader { struct BypassScope : IDisposable { readonly XRInputButtonReader m_Reader; public BypassScope(XRInputButtonReader reader) { m_Reader = reader; m_Reader.m_CallingBypass = true; } public void Dispose() { m_Reader.m_CallingBypass = false; } } /// /// The mode that determines from which input source the button's state is read from. /// /// public enum InputSourceMode { /// /// The input is explicitly not used. /// Set to this mode to avoid any performance cost when the input should be ignored. /// Unused, /// /// The input is read from an input action defined and serialized with this behavior. /// /// /// /// /// InputAction, /// /// The input is read from an input action defined in the project. /// This is the default mode. /// /// /// InputActionReference, /// /// The input is read from an object reference that implements . /// /// /// ObjectReference, /// /// The input is returned from manually set values, either in the Inspector window or at runtime through scripting. /// /// /// /// /// ManualValue, } [SerializeField] InputSourceMode m_InputSourceMode = InputSourceMode.InputActionReference; /// /// The mode that determines from which input source the button's state is read from. /// By default this is set to to read from an input action /// defined in the project. /// /// public InputSourceMode inputSourceMode { get => m_InputSourceMode; set => m_InputSourceMode = value; } [SerializeField] InputAction m_InputActionPerformed; /// /// The directly serialized embedded input action that is read to determine whether the button is down /// when the mode is set to . /// /// /// Must have a button-like interaction where phase equals performed when down. /// This is the case with an input action whose Action Type is Button /// or whose Action Type is Value with an interaction like Press or Sector. /// public InputAction inputActionPerformed { get => m_InputActionPerformed; set => m_InputActionPerformed = value; } [SerializeField] InputAction m_InputActionValue; /// /// The directly serialized embedded input action that is read to determine the scalar value that varies from 0 to 1 /// when the mode is set to . /// public InputAction inputActionValue { get => m_InputActionValue; set => m_InputActionValue = value; } [SerializeField] InputActionReference m_InputActionReferencePerformed; /// /// The reference to an input action that is read to determine whether the button is down /// when the mode is set to . /// /// /// Must have a button-like interaction where phase equals performed when down. /// This is the case with an input action whose Action Type is Button /// or whose Action Type is Value with an interaction like Press or Sector. /// public InputActionReference inputActionReferencePerformed { get => m_InputActionReferencePerformed; set => m_InputActionReferencePerformed = value; } [SerializeField] InputActionReference m_InputActionReferenceValue; /// /// The reference to an input action that is read to determine the scalar value that varies from 0 to 1 /// when the mode is set to . /// public InputActionReference inputActionReferenceValue { get => m_InputActionReferenceValue; set => m_InputActionReferenceValue = value; } [SerializeField] [RequireInterface(typeof(IXRInputButtonReader))] Object m_ObjectReferenceObject; [SerializeField] bool m_ManualPerformed; /// /// The manually set performed state that is returned /// when the mode is set to . /// /// /// /// public bool manualPerformed { get => m_ManualPerformed; set => m_ManualPerformed = value; } [SerializeField, Range(0f, 1f)] float m_ManualValue; /// /// The manually set scalar value that varies from 0 to 1 that is returned /// when the mode is set to . /// public float manualValue { get => m_ManualValue; set => m_ManualValue = value; } // Serialized to allow the property drawer to queue for the next frame, repeating logic in QueueManualState [SerializeField] bool m_ManualQueuePerformed; [SerializeField] bool m_ManualQueueWasPerformedThisFrame; [SerializeField] bool m_ManualQueueWasCompletedThisFrame; [SerializeField] float m_ManualQueueValue; [SerializeField] int m_ManualQueueTargetFrame; // Not serialized int m_ManualFramePerformed; /// /// The frame that the manual performed state was set to /// when the mode is set to . /// /// /// public int manualFramePerformed { get => m_ManualFramePerformed; set => m_ManualFramePerformed = value; } // Not serialized int m_ManualFrameCompleted; /// /// The frame that the manual performed state was set to /// when the mode is set to . /// /// /// public int manualFrameCompleted { get => m_ManualFrameCompleted; set => m_ManualFrameCompleted = value; } /// /// A runtime bypass that can be used to override the button input state returned by this class. /// public IXRInputButtonReader bypass { get; set; } /// /// Whether this class is calling into the bypass. /// The purpose of this is to allow the bypass to read the raw value of this class using the public methods /// without recursively calling into the bypass again. /// bool m_CallingBypass; readonly UnityObjectReferenceCache m_ObjectReference = new UnityObjectReferenceCache(); readonly UnityObjectReferenceCache m_InputActionReferencePerformedCache = new UnityObjectReferenceCache(); readonly UnityObjectReferenceCache m_InputActionReferenceValueCache = new UnityObjectReferenceCache(); /// /// Initializes and returns an instance of . /// public XRInputButtonReader() { } /// /// Initializes and returns an instance of . /// /// The name of the directly serialized embedded input action for performed. /// The name of the directly serialized embedded input action for the scalar value. Based off of if left . /// Whether the action should start performing if already actuated when the action is enabled. /// The initial input source mode. public XRInputButtonReader(string name = null, string valueName = null, bool wantsInitialStateCheck = false, InputSourceMode inputSourceMode = InputSourceMode.InputActionReference) { m_InputActionPerformed = InputActionUtility.CreateButtonAction(name, wantsInitialStateCheck); m_InputActionValue = InputActionUtility.CreateValueAction(typeof(float), valueName ?? (name != null ? (name + " Value") : null)); m_InputSourceMode = inputSourceMode; } /// /// Enable the directly serialized embedded input action if the mode is set to . /// /// public void EnableDirectActionIfModeUsed() { if (m_InputSourceMode == InputSourceMode.InputAction) { m_InputActionPerformed.Enable(); m_InputActionValue.Enable(); } } /// /// Disable the directly serialized embedded input action if the mode is set to . /// /// public void DisableDirectActionIfModeUsed() { if (m_InputSourceMode == InputSourceMode.InputAction) { m_InputActionPerformed.Disable(); m_InputActionValue.Disable(); } } /// /// Gets the object reference that is used as the input source /// when the mode is set to . /// /// Returns the object reference, which may be . public IXRInputButtonReader GetObjectReference() { return m_ObjectReference.Get(m_ObjectReferenceObject); } /// /// Sets the object reference that is used as the input source /// when the mode is set to . /// /// The object reference. /// /// If the argument is to be serialized, it must be a Unity type. /// public void SetObjectReference(IXRInputButtonReader value) { m_ObjectReference.Set(ref m_ObjectReferenceObject, value); } /// /// Queue a manual state to be effective on the next frame. /// This method automatically determines whether the button should be considered as newly performed that frame based on the current state. /// /// The manual button performed state that should be returned next frame. /// The manual scalar value that varies from 0 to 1 that should be returned next frame. public void QueueManualState(bool performed, float value) => QueueManualState(performed, value, !m_ManualPerformed && performed, m_ManualPerformed && !performed); /// /// Queue a manual state to be effective on the next frame. /// /// The manual button performed state that should be returned next frame. /// The manual scalar value that varies from 0 to 1 that should be returned next frame. /// Whether the manual button should be considered as performed that frame on the next frame. /// Whether the manual button should be considered as completed that frame on the next frame. public void QueueManualState(bool performed, float value, bool performedThisFrame, bool completedThisFrame) { if (m_InputSourceMode != InputSourceMode.ManualValue) Debug.LogWarning($"QueueManualState was called but the input source mode is set to {m_InputSourceMode}." + $"You may want to set {nameof(inputSourceMode)} to {nameof(InputSourceMode.ManualValue)} for the manual state to be effective next frame."); m_ManualQueuePerformed = performed; m_ManualQueueWasPerformedThisFrame = performedThisFrame; m_ManualQueueWasCompletedThisFrame = completedThisFrame; m_ManualQueueValue = value; m_ManualQueueTargetFrame = Time.frameCount + 1; } void RefreshManualIfNeeded() { if (m_ManualQueueTargetFrame > 0 && Time.frameCount >= m_ManualQueueTargetFrame) { m_ManualPerformed = m_ManualQueuePerformed; if (m_ManualQueueWasPerformedThisFrame) m_ManualFramePerformed = Time.frameCount; if (m_ManualQueueWasCompletedThisFrame) m_ManualFrameCompleted = Time.frameCount; m_ManualValue = m_ManualQueueValue; m_ManualQueueTargetFrame = 0; } } /// public bool ReadIsPerformed() { if (bypass != null && !m_CallingBypass) { using (new BypassScope(this)) { return bypass.ReadIsPerformed(); } } switch (m_InputSourceMode) { case InputSourceMode.Unused: default: return false; case InputSourceMode.InputAction: return IsPerformed(m_InputActionPerformed); case InputSourceMode.InputActionReference: return TryGetInputActionReferencePerformed(out var reference) && IsPerformed(reference.action); case InputSourceMode.ObjectReference: { var objectReference = GetObjectReference(); return objectReference?.ReadIsPerformed() ?? false; } case InputSourceMode.ManualValue: RefreshManualIfNeeded(); return m_ManualPerformed; } } /// public bool ReadWasPerformedThisFrame() { if (bypass != null && !m_CallingBypass) { using (new BypassScope(this)) { return bypass.ReadWasPerformedThisFrame(); } } switch (m_InputSourceMode) { case InputSourceMode.Unused: default: return false; case InputSourceMode.InputAction: return WasPerformedThisFrame(m_InputActionPerformed); case InputSourceMode.InputActionReference: return TryGetInputActionReferencePerformed(out var reference) && WasPerformedThisFrame(reference.action); case InputSourceMode.ObjectReference: { var objectReference = GetObjectReference(); return objectReference?.ReadWasPerformedThisFrame() ?? false; } case InputSourceMode.ManualValue: RefreshManualIfNeeded(); return m_ManualPerformed && m_ManualFramePerformed == Time.frameCount; } } /// public bool ReadWasCompletedThisFrame() { if (bypass != null && !m_CallingBypass) { using (new BypassScope(this)) { return bypass.ReadWasCompletedThisFrame(); } } switch (m_InputSourceMode) { case InputSourceMode.Unused: default: return false; case InputSourceMode.InputAction: return WasCompletedThisFrame(m_InputActionPerformed); case InputSourceMode.InputActionReference: return TryGetInputActionReferencePerformed(out var reference) && WasCompletedThisFrame(reference.action); case InputSourceMode.ObjectReference: { var objectReference = GetObjectReference(); return objectReference?.ReadWasCompletedThisFrame() ?? false; } case InputSourceMode.ManualValue: RefreshManualIfNeeded(); return !m_ManualPerformed && m_ManualFrameCompleted == Time.frameCount; } } /// public float ReadValue() { if (bypass != null && !m_CallingBypass) { using (new BypassScope(this)) { return bypass.ReadValue(); } } switch (m_InputSourceMode) { case InputSourceMode.Unused: default: return default; case InputSourceMode.InputAction: return ReadValueToFloat(m_InputActionValue); case InputSourceMode.InputActionReference: return TryGetInputActionReferenceValue(out var reference) ? ReadValueToFloat(reference.action) : default; case InputSourceMode.ObjectReference: { var objectReference = GetObjectReference(); return objectReference?.ReadValue() ?? default; } case InputSourceMode.ManualValue: RefreshManualIfNeeded(); return m_ManualValue; } } /// public bool TryReadValue(out float value) { if (bypass != null && !m_CallingBypass) { using (new BypassScope(this)) { return bypass.TryReadValue(out value); } } switch (m_InputSourceMode) { case InputSourceMode.Unused: default: value = default; return false; case InputSourceMode.InputAction: return TryReadValue(m_InputActionValue, out value); case InputSourceMode.InputActionReference: if (TryGetInputActionReferenceValue(out var reference)) return TryReadValue(reference.action, out value); value = default; return false; case InputSourceMode.ObjectReference: { var objectReference = GetObjectReference(); if (objectReference != null) return objectReference.TryReadValue(out value); value = default; return false; } case InputSourceMode.ManualValue: RefreshManualIfNeeded(); value = m_ManualValue; return true; } } static bool IsPerformed(InputAction action) { if (action == null) return false; var phase = action.phase; return phase == InputActionPhase.Performed || (phase != InputActionPhase.Disabled && action.WasPerformedThisFrame()); } static bool WasPerformedThisFrame(InputAction action) { return action != null && action.WasPerformedThisFrame(); } static bool WasCompletedThisFrame(InputAction action) { return action != null && action.WasCompletedThisFrame(); } float ReadValueToFloat(InputAction action) { if (action == null) return default; // Processors such as AxisDeadzone or ScaleVector2 can cause the action's value (obtained with ReadValue) // to be different from the control's magnitude (obtained with GetControlMagnitude). // Evaluating whether the root binding, composite binding, or the action has any processors is not trivial. // We evaluate the magnitude of the read Vector2 value instead of using GetControlMagnitude to be safe. var activeValueType = action.activeValueType; if (activeValueType == null || activeValueType == typeof(float)) return action.ReadValue(); if (activeValueType == typeof(Vector2)) return action.ReadValue().magnitude; return Mathf.Max(action.GetControlMagnitude(), 0f); } bool TryReadValue(InputAction action, out float value) { if (action == null) { value = default; return false; } value = ReadValueToFloat(action); return action.IsInProgress(); } bool TryGetInputActionReferencePerformed(out InputActionReference reference) => m_InputActionReferencePerformedCache.TryGet(m_InputActionReferencePerformed, out reference); bool TryGetInputActionReferenceValue(out InputActionReference reference) => m_InputActionReferenceValueCache.TryGet(m_InputActionReferenceValue, out reference); } }