2006 lines
96 KiB
C#
2006 lines
96 KiB
C#
using System;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using UnityEngine.InputSystem.Controls;
|
|
using UnityEngine.InputSystem.LowLevel;
|
|
using UnityEngine.InputSystem.Utilities;
|
|
using UnityEngine.Serialization;
|
|
|
|
////TODO: add way to retrieve the binding correspond to a control
|
|
|
|
////TODO: add way to retrieve the currently ongoing interaction and also add way to know how long it's been going on
|
|
|
|
////FIXME: control goes back to invalid when the action ends, so there's no guarantee you can get to the control through the polling API
|
|
|
|
////FIXME: Whether a control from a binding that's part of a composite appears on an action is currently not consistently enforced.
|
|
//// If it mentions the action, it appears on the action. Otherwise it doesn't. The controls should consistently appear on the
|
|
//// action based on what action the *composite* references.
|
|
|
|
////REVIEW: Should we bring the checkboxes for actions back? We tried to "simplify" things by collapsing everything into a InputActionTypes
|
|
//// and making the various behavior toggles implicit in that. However, my impression is that this has largely backfired by making
|
|
//// it opaque what the choices actually entail and by giving no way out if the choices for one reason or another don't work out
|
|
//// perfectly.
|
|
////
|
|
//// My impression is that at least two the following two checkboxes would make sense:
|
|
//// 1) Initial State Check? Whether the action should immediately sync to the current state of controls when enabled.
|
|
//// 2) Resolve Conflicting Inputs? Whether the action should try to resolve conflicts between multiple concurrent inputs.
|
|
////
|
|
//// I'm fine hiding this under an "Advanced" foldout or something. But IMO, control over this should be available to the user.
|
|
////
|
|
//// In the same vein, we probably also should expose control over how an action behaves on focus loss (https://forum.unity.com/threads/actions-canceled-when-game-loses-focus.855217/).
|
|
|
|
////REVIEW: I think the action system as it is today offers too many ways to shoot yourself in the foot. It has
|
|
//// flexibility but at the same time has abundant opportunity for ending up with dysfunction. Common setups
|
|
//// have to come preconfigured and work robustly for the user without requiring much understanding of how
|
|
//// the system fits together.
|
|
|
|
////REVIEW: add "lastControl" property? (and maybe a lastDevice at the InputActionMap/Asset level?)
|
|
|
|
////REVIEW: have single delegate instead of separate performed/started/canceled callbacks?
|
|
|
|
////REVIEW: Do we need to have separate display names for actions?
|
|
|
|
////REVIEW: what about having the concept of "consumed" on the callback context?
|
|
|
|
////REVIEW: have "Always Enabled" toggle on actions?
|
|
|
|
////TODO: allow temporarily disabling individual bindings (flag on binding) such that no re-resolve is needed
|
|
//// (SilenceBinding? DisableBinding)
|
|
|
|
namespace UnityEngine.InputSystem
|
|
{
|
|
/// <summary>
|
|
/// A named input signal that can flexibly decide which input data to tap.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// An input action is an abstraction over the source of input(s) it receives. They are
|
|
/// most useful for representing input as "logical" concepts (e.g. "jump") rather than
|
|
/// as "physical" inputs (e.g. "space bar on keyboard pressed").
|
|
///
|
|
/// In its most basic form, an action is simply an object along with a collection of
|
|
/// bindings that trigger the action.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // A simple action can be created directly using `new`. If desired, a binding
|
|
/// // can be specified directly as part of construction.
|
|
/// var action = new InputAction(binding: "<Gamepad>/buttonSouth");
|
|
///
|
|
/// // Additional bindings can be added using `AddBinding`.
|
|
/// action.AddBinding("<Mouse>/leftButton");
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Bindings use control path expressions to reference controls. See <see cref="InputBinding"/>
|
|
/// for more details. There may be arbitrary many bindings targeting a single action. The
|
|
/// list of bindings targeting an action can be obtained through <see cref="bindings"/>.
|
|
///
|
|
/// By itself an action does not do anything until it is enabled:
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// action.Enable();
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Once enabled, the action will actively monitor all controls on devices present
|
|
/// in the system (see <see cref="InputSystem.devices"/>) that match any of the binding paths
|
|
/// associated with the action. If you want to restrict the set of bindings used at runtime
|
|
/// or restrict the set of devices which controls are chosen from, you can do so using
|
|
/// <see cref="bindingMask"/> or, if the action is part of an <see cref="InputActionMap"/>,
|
|
/// by setting the <see cref="InputActionMap.devices"/> property of the action map. The
|
|
/// controls that an action uses can be queried using the <see cref="controls"/> property.
|
|
///
|
|
/// When input is received on controls bound to an action, the action will trigger callbacks
|
|
/// in response. These callbacks are <see cref="started"/>, <see cref="performed"/>, and
|
|
/// <see cref="canceled"/>. The callbacks are triggered as part of input system updates
|
|
/// (see <see cref="InputSystem.Update"/>), i.e. they happen before the respective
|
|
/// <c>MonoBehaviour.Update</c> or <c>MonoBehaviour.FixedUpdate</c> methods
|
|
/// get executed (depending on which <see cref="InputSettings.updateMode"/> the system is
|
|
/// set to).
|
|
///
|
|
/// In what order and how those callbacks get triggered depends on both the <see cref="type"/>
|
|
/// of the action as well as on the interactions (see <see cref="IInputInteraction"/>) present
|
|
/// on the bindings of the action. The default behavior is that when a control is actuated
|
|
/// (that is, moving away from its resting position), <see cref="started"/> is called and then
|
|
/// <see cref="performed"/>. Subsequently, whenever the a control further changes value to
|
|
/// anything other than its default value, <see cref="performed"/> will be called again.
|
|
/// Finally, when the control moves back to its default value (i.e. resting position),
|
|
/// <see cref="canceled"/> is called.
|
|
///
|
|
/// To hook into the callbacks, there are several options available to you. The most obvious
|
|
/// one is to hook directly into <see cref="started"/>, <see cref="performed"/>, and/or
|
|
/// <see cref="canceled"/>. In these callbacks, you will receive a <see cref="CallbackContext"/>
|
|
/// with information about how the action got triggered. For example, you can use <see
|
|
/// cref="CallbackContext.ReadValue{TValue}"/> to read the value from the binding that triggered
|
|
/// or use <see cref="CallbackContext.interaction"/> to find the interaction that is in progress.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// action.started += context => Debug.Log($"{context.action} started");
|
|
/// action.performed += context => Debug.Log($"{context.action} performed");
|
|
/// action.canceled += context => Debug.Log($"{context.action} canceled");
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Alternatively, you can use the <see cref="InputActionMap.actionTriggered"/> callback for
|
|
/// actions that are part of an action map or the global <see cref="InputSystem.onActionChange"/>
|
|
/// callback to globally listen for action activity. To simply record action activity instead
|
|
/// of responding to it directly, you can use <see cref="InputActionTrace"/>.
|
|
///
|
|
/// If you prefer to poll an action directly as part of your <c>MonoBehaviour.Update</c>
|
|
/// or <c>MonoBehaviour.FixedUpdate</c> logic, you can do so using the <see cref="triggered"/>
|
|
/// and <see cref="ReadValue{TValue}"/> methods.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// protected void Update()
|
|
/// {
|
|
/// // For a button type action.
|
|
/// if (action.triggered)
|
|
/// /* ... */;
|
|
///
|
|
/// // For a value type action.
|
|
/// // (Vector2 is just an example; pick the value type that is the right
|
|
/// // one according to the bindings you have)
|
|
/// var v = action.ReadValue<Vector2>();
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Note that actions are not generally frame-based. What this means is that an action
|
|
/// will observe any value change on its connected controls, even if the control changes
|
|
/// value multiple times in the same frame. In practice, this means that, for example,
|
|
/// no button press will get missed.
|
|
///
|
|
/// Actions can be grouped into maps (see <see cref="InputActionMap"/>) which can in turn
|
|
/// be grouped into assets (see <see cref="InputActionAsset"/>).
|
|
///
|
|
/// Please note that actions are a player-only feature. They are not supported in
|
|
/// edit mode.
|
|
///
|
|
/// For more in-depth reading on actions, see the <a href="../manual/Actions.html">manual</a>.
|
|
/// </remarks>
|
|
/// <seealso cref="InputActionMap"/>
|
|
/// <seealso cref="InputActionAsset"/>
|
|
/// <seealso cref="InputBinding"/>
|
|
[Serializable]
|
|
public sealed class InputAction : ICloneable, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Name of the action.
|
|
/// </summary>
|
|
/// <value>Plain-text name of the action.</value>
|
|
/// <remarks>
|
|
/// Can be null for anonymous actions created in code.
|
|
///
|
|
/// If the action is part of an <see cref="InputActionMap"/>, it will have a name and the name
|
|
/// will be unique in the map. The name is just the name of the action alone, not a "mapName/actionName"
|
|
/// combination.
|
|
///
|
|
/// The name should not contain slashes or dots but can contain spaces and other punctuation.
|
|
///
|
|
/// An action can be renamed after creation using <see cref="InputActionSetupExtensions.Rename"/>..
|
|
/// </remarks>
|
|
/// <seealso cref="InputActionMap.FindAction(string,bool)"/>
|
|
public string name => m_Name;
|
|
|
|
/// <summary>
|
|
/// Behavior type of the action.
|
|
/// </summary>
|
|
/// <value>General behavior type of the action.</value>
|
|
/// <remarks>
|
|
/// Determines how the action gets triggered in response to control value changes.
|
|
///
|
|
/// For details about how the action type affects an action, see <see cref="InputActionType"/>.
|
|
/// </remarks>
|
|
public InputActionType type => m_Type;
|
|
|
|
/// <summary>
|
|
/// A stable, unique identifier for the action.
|
|
/// </summary>
|
|
/// <value>Unique ID of the action.</value>
|
|
/// <remarks>
|
|
/// This can be used instead of the name to refer to the action. Doing so allows referring to the
|
|
/// action such that renaming the action does not break references.
|
|
/// </remarks>
|
|
public Guid id
|
|
{
|
|
get
|
|
{
|
|
MakeSureIdIsInPlace();
|
|
return new Guid(m_Id);
|
|
}
|
|
}
|
|
|
|
internal Guid idDontGenerate
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(m_Id))
|
|
return default;
|
|
return new Guid(m_Id);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name of control layout expected for controls bound to this action.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is optional and is null by default.
|
|
///
|
|
/// Constraining an action to a particular control layout allows determine the value
|
|
/// type and expected input behavior of an action without being reliant on any particular
|
|
/// binding.
|
|
/// </remarks>
|
|
public string expectedControlType
|
|
{
|
|
get => m_ExpectedControlType;
|
|
set => m_ExpectedControlType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processors applied to every binding on the action.
|
|
/// </summary>
|
|
/// <value>Processors added to all bindings on the action.</value>
|
|
/// <remarks>
|
|
/// This property is equivalent to appending the same string to the
|
|
/// <see cref="InputBinding.processors"/> field of every binding that targets
|
|
/// the action. It is thus simply a means of avoiding the need configure the
|
|
/// same processor the same way on every binding in case it uniformly applies
|
|
/// to all of them.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var action = new InputAction(processors: "scaleVector2(x=2, y=2)");
|
|
///
|
|
/// // Both of the following bindings will implicitly have a
|
|
/// // ScaleVector2Processor applied to them.
|
|
/// action.AddBinding("<Gamepad>/leftStick");
|
|
/// action.AddBinding("<Joystick>/stick");
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputBinding.processors"/>
|
|
/// <seealso cref="InputProcessor"/>
|
|
/// <seealso cref="InputSystem.RegisterProcessor{T}"/>
|
|
public string processors => m_Processors;
|
|
|
|
/// <summary>
|
|
/// Interactions applied to every binding on the action.
|
|
/// </summary>
|
|
/// <value>Interactions added to all bindings on the action.</value>
|
|
/// <remarks>
|
|
/// This property is equivalent to appending the same string to the
|
|
/// <see cref="InputBinding.interactions"/> field of every binding that targets
|
|
/// the action. It is thus simply a means of avoiding the need configure the
|
|
/// same interaction the same way on every binding in case it uniformly applies
|
|
/// to all of them.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var action = new InputAction(interactions: "press");
|
|
///
|
|
/// // Both of the following bindings will implicitly have a
|
|
/// // Press interaction applied to them.
|
|
/// action.AddBinding("<Gamepad>/buttonSouth");
|
|
/// action.AddBinding("<Joystick>/trigger");
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputBinding.interactions"/>
|
|
/// <seealso cref="IInputInteraction"/>
|
|
/// <seealso cref="InputSystem.RegisterInteraction{T}"/>
|
|
public string interactions => m_Interactions;
|
|
|
|
/// <summary>
|
|
/// The map the action belongs to.
|
|
/// </summary>
|
|
/// <value><see cref="InputActionMap"/> that the action belongs to or null.</value>
|
|
/// <remarks>
|
|
/// If the action is a loose action created in code, this will be <c>null</c>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var action1 = new InputAction(); // action1.actionMap will be null
|
|
///
|
|
/// var actionMap = new InputActionMap();
|
|
/// var action2 = actionMap.AddAction("action"); // action2.actionMap will point to actionMap
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputActionSetupExtensions.AddAction"/>
|
|
public InputActionMap actionMap => isSingletonAction ? null : m_ActionMap;
|
|
|
|
/// <summary>
|
|
/// An optional mask that determines which bindings of the action to enable and
|
|
/// which to ignore.
|
|
/// </summary>
|
|
/// <value>Optional mask that determines which bindings on the action to enable.</value>
|
|
/// <remarks>
|
|
/// Binding masks can be applied at three different levels: for an entire asset through
|
|
/// <see cref="InputActionAsset.bindingMask"/>, for a specific map through <see
|
|
/// cref="InputActionMap.bindingMask"/>, and for single actions through this property.
|
|
/// By default, none of the masks will be set (i.e. they will be <c>null</c>).
|
|
///
|
|
/// When an action is enabled, all the binding masks that apply to it are taken into
|
|
/// account. Specifically, this means that any given binding on the action will be
|
|
/// enabled only if it matches the mask applied to the asset, the mask applied
|
|
/// to the map that contains the action, and the mask applied to the action itself.
|
|
/// All the masks are individually optional.
|
|
///
|
|
/// Masks are matched against bindings using <see cref="InputBinding.Matches"/>.
|
|
///
|
|
/// Note that if you modify the masks applicable to an action while it is
|
|
/// enabled, the action's <see cref="controls"/> will get updated immediately to
|
|
/// respect the mask. To avoid repeated binding resolution, it is most efficient
|
|
/// to apply binding masks before enabling actions.
|
|
///
|
|
/// Binding masks are non-destructive. All the bindings on the action are left
|
|
/// in place. Setting a mask will not affect the value of the <see cref="bindings"/>
|
|
/// property.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Create a free-standing action with two bindings, one in the
|
|
/// // "Keyboard" group and one in the "Gamepad" group.
|
|
/// var action = new InputAction();
|
|
/// action.AddBinding("<Gamepad>/buttonSouth", groups: "Gamepad");
|
|
/// action.AddBinding("<Keyboard>/space", groups: "Keyboard");
|
|
///
|
|
/// // By default, all bindings will be enabled. This means if both
|
|
/// // a keyboard and gamepad (or several of them) is present, the action
|
|
/// // will respond to input from all of them.
|
|
/// action.Enable();
|
|
///
|
|
/// // With a binding mask we can restrict the action to just specific
|
|
/// // bindings. For example, to only enable the gamepad binding:
|
|
/// action.bindingMask = InputBinding.MaskByGroup("Gamepad");
|
|
///
|
|
/// // Note that we can mask by more than just by group. Masking by path
|
|
/// // or by action as well as a combination of these is also possible.
|
|
/// // We could, for example, mask for just a specific binding path:
|
|
/// action.bindingMask = new InputBinding()
|
|
/// {
|
|
/// // Select the keyboard binding based on its specific path.
|
|
/// path = "<Keyboard>/space"
|
|
/// };
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputBinding.MaskByGroup"/>
|
|
/// <seealso cref="InputActionMap.bindingMask"/>
|
|
/// <seealso cref="InputActionAsset.bindingMask"/>
|
|
public InputBinding? bindingMask
|
|
{
|
|
get => m_BindingMask;
|
|
set
|
|
{
|
|
if (value == m_BindingMask)
|
|
return;
|
|
|
|
if (value != null)
|
|
{
|
|
var v = value.Value;
|
|
v.action = name;
|
|
value = v;
|
|
}
|
|
|
|
m_BindingMask = value;
|
|
|
|
var map = GetOrCreateActionMap();
|
|
if (map.m_State != null)
|
|
map.LazyResolveBindings(fullResolve: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The list of bindings associated with the action.
|
|
/// </summary>
|
|
/// <value>List of bindings for the action.</value>
|
|
/// <remarks>
|
|
/// This list contains all bindings from <see cref="InputActionMap.bindings"/> of the action's
|
|
/// <see cref="actionMap"/> that reference the action through their <see cref="InputBinding.action"/>
|
|
/// property.
|
|
///
|
|
/// Note that on the first call, the list may have to be extracted from the action map first which
|
|
/// may require allocating GC memory. However, once initialized, no further GC allocation hits should occur.
|
|
/// If the binding setup on the map is changed, re-initialization may be required.
|
|
/// </remarks>
|
|
/// <seealso cref="InputActionMap.bindings"/>
|
|
public ReadOnlyArray<InputBinding> bindings => GetOrCreateActionMap().GetBindingsForSingleAction(this);
|
|
|
|
/// <summary>
|
|
/// The set of controls to which the action's <see cref="bindings"/> resolve.
|
|
/// </summary>
|
|
/// <value>Controls resolved from the action's <see cref="bindings"/>.</value>
|
|
/// <remarks>
|
|
/// This property can be queried whether the action is enabled or not and will return the
|
|
/// set of controls that match the action's bindings according to the current setup of
|
|
/// binding masks (<see cref="bindingMask"/>) and device restrictions (<see
|
|
/// cref="InputActionMap.devices"/>).
|
|
///
|
|
/// Note that internally, controls are not stored on a per-action basis. This means
|
|
/// that on the first read of this property, the list of controls for just the action
|
|
/// may have to be extracted which in turn may allocate GC memory. After the first read,
|
|
/// no further GC allocations should occur except if the set of controls is changed (e.g.
|
|
/// by changing the binding mask or by adding/removing devices to/from the system).
|
|
///
|
|
/// If the property is queried when the action has not been enabled yet, the system
|
|
/// will first resolve controls on the action (and for all actions in the map and/or
|
|
/// the asset). See <a href="../manual/ActionBindings.html#binding-resolution">Binding Resolution</a>
|
|
/// in the manual for details.
|
|
///
|
|
/// To map a control in this array to an index into <see cref="bindings"/>, use
|
|
/// <see cref="InputActionRebindingExtensions.GetBindingIndexForControl"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Map control list to binding indices.
|
|
/// var bindingIndices = myAction.controls.Select(c => myAction.GetBindingIndexForControl(c));
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Note that this array will not contain the same control multiple times even if more than
|
|
/// one binding on an action references the same control.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var action1 = new InputAction();
|
|
/// action1.AddBinding("<Gamepad>/buttonSouth");
|
|
/// action1.AddBinding("<Gamepad>/buttonSouth"); // This binding will be ignored.
|
|
///
|
|
/// // Contains only one instance of buttonSouth which is associated
|
|
/// // with the first binding (at index #0).
|
|
/// var action1Controls = action1.controls;
|
|
///
|
|
/// var action2 = new InputAction();
|
|
/// action2.AddBinding("<Gamepad>/buttonSouth");
|
|
/// // Add a binding that implicitly matches the first binding, too. When binding resolution
|
|
/// // happens, this binding will only receive buttonNorth, buttonWest, and buttonEast, but not
|
|
/// // buttonSouth as the first binding already received that control.
|
|
/// action2.AddBinding("<Gamepad>/button*");
|
|
///
|
|
/// // Contains only all four face buttons (buttonSouth, buttonNorth, buttonEast, buttonWest)
|
|
/// // but buttonSouth is associated with the first button and only buttonNorth, buttonEast,
|
|
/// // and buttonWest are associated with the second binding.
|
|
/// var action2Controls = action2.controls;
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputActionRebindingExtensions.GetBindingIndexForControl"/>
|
|
/// <seealso cref="bindings"/>
|
|
public ReadOnlyArray<InputControl> controls
|
|
{
|
|
get
|
|
{
|
|
var map = GetOrCreateActionMap();
|
|
map.ResolveBindingsIfNecessary();
|
|
return map.GetControlsForSingleAction(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The current phase of the action.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// When listening for control input and when responding to control value changes,
|
|
/// actions will go through several possible phases.
|
|
///
|
|
/// In general, when an action starts receiving input, it will go to <see cref="InputActionPhase.Started"/>
|
|
/// and when it stops receiving input, it will go to <see cref="InputActionPhase.Canceled"/>.
|
|
/// When <see cref="InputActionPhase.Performed"/> is used depends primarily on the type
|
|
/// of action. <see cref="InputActionType.Value"/> will trigger <see cref="InputActionPhase.Performed"/>
|
|
/// whenever the value of the control changes (including the first time; i.e. it will first
|
|
/// trigger <see cref="InputActionPhase.Started"/> and then <see cref="InputActionPhase.Performed"/>
|
|
/// right after) whereas <see cref="InputActionType.Button"/> will trigger <see cref="InputActionPhase.Performed"/>
|
|
/// as soon as the button press threshold (<see cref="InputSettings.defaultButtonPressPoint"/>)
|
|
/// has been crossed.
|
|
///
|
|
/// Note that both interactions and the action <see cref="type"/> can affect the phases
|
|
/// that an action goes through. <see cref="InputActionType.PassThrough"/> actions will
|
|
/// only ever use <see cref="InputActionPhase.Performed"/> and not go to <see
|
|
/// cref="InputActionPhase.Started"/> or <see cref="InputActionPhase.Canceled"/> (as
|
|
/// pass-through actions do not follow the start-performed-canceled model in general).
|
|
/// Also, interactions can choose their
|
|
///
|
|
/// While an action is disabled, its phase is <see cref="InputActionPhase.Disabled"/>.
|
|
/// </remarks>
|
|
public InputActionPhase phase => currentState.phase;
|
|
|
|
/// <summary>
|
|
/// True if the action is currently in <see cref="InputActionPhase.Started"/> or <see cref="InputActionPhase.Performed"/>
|
|
/// phase. False in all other cases.
|
|
/// </summary>
|
|
/// <see cref="phase"/>
|
|
public bool inProgress => phase.IsInProgress();
|
|
|
|
/// <summary>
|
|
/// Whether the action is currently enabled, i.e. responds to input, or not.
|
|
/// </summary>
|
|
/// <value>True if the action is currently enabled.</value>
|
|
/// <remarks>
|
|
/// An action is enabled by either calling <see cref="Enable"/> on it directly or by calling
|
|
/// <see cref="InputActionMap.Enable"/> on the <see cref="InputActionMap"/> containing the action.
|
|
/// When enabled, an action will listen for changes on the controls it is bound to and trigger
|
|
/// callbacks such as <see cref="started"/>, <see cref="performed"/>, and <see cref="canceled"/>
|
|
/// in response.
|
|
/// </remarks>
|
|
/// <seealso cref="Enable"/>
|
|
/// <seealso cref="Disable"/>
|
|
/// <seealso cref="InputActionMap.Enable"/>
|
|
/// <seealso cref="InputActionMap.Disable"/>
|
|
/// <seealso cref="InputSystem.ListEnabledActions()"/>
|
|
public bool enabled => phase != InputActionPhase.Disabled;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when the action has been started.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see cref="phase"/> for details of how an action progresses through phases
|
|
/// and triggers this callback.
|
|
/// </remarks>
|
|
/// <see cref="InputActionPhase.Started"/>
|
|
public event Action<CallbackContext> started
|
|
{
|
|
add => m_OnStarted.AddCallback(value);
|
|
remove => m_OnStarted.RemoveCallback(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when the action has been <see cref="started"/>
|
|
/// but then canceled before being fully <see cref="performed"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see cref="phase"/> for details of how an action progresses through phases
|
|
/// and triggers this callback.
|
|
/// </remarks>
|
|
/// <see cref="InputActionPhase.Canceled"/>
|
|
public event Action<CallbackContext> canceled
|
|
{
|
|
add => m_OnCanceled.AddCallback(value);
|
|
remove => m_OnCanceled.RemoveCallback(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when the action has been fully performed.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// See <see cref="phase"/> for details of how an action progresses through phases
|
|
/// and triggers this callback.
|
|
/// </remarks>
|
|
/// <see cref="InputActionPhase.Performed"/>
|
|
public event Action<CallbackContext> performed
|
|
{
|
|
add => m_OnPerformed.AddCallback(value);
|
|
remove => m_OnPerformed.RemoveCallback(value);
|
|
}
|
|
|
|
////TODO: Obsolete and drop this when we can break API
|
|
/// <summary>
|
|
/// Equivalent to <see cref="WasPerformedThisFrame"/>.
|
|
/// </summary>
|
|
/// <seealso cref="WasPerformedThisFrame"/>
|
|
public bool triggered => WasPerformedThisFrame();
|
|
|
|
/// <summary>
|
|
/// The currently active control that is driving the action. Null while the action
|
|
/// is in waiting (<see cref="InputActionPhase.Waiting"/>) or canceled (<see cref="InputActionPhase.Canceled"/>)
|
|
/// state. Otherwise the control that last had activity on it which wasn't ignored.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Note that the control's value does not necessarily correspond to the value of the
|
|
/// action (<see cref="ReadValue{TValue}"/>) as the control may be part of a composite.
|
|
/// </remarks>
|
|
/// <seealso cref="CallbackContext.control"/>
|
|
public unsafe InputControl activeControl
|
|
{
|
|
get
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
var controlIndex = actionStatePtr->controlIndex;
|
|
if (controlIndex != InputActionState.kInvalidIndex)
|
|
return state.controls[controlIndex];
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the action wants a state check on its bound controls as soon as it is enabled. This is always
|
|
/// true for <see cref="InputActionType.Value"/> actions but can optionally be enabled for <see cref="InputActionType.Button"/>
|
|
/// or <see cref="InputActionType.PassThrough"/> actions.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Usually, when an action is <see cref="enabled"/> (e.g. via <see cref="Enable"/>), it will start listening for input
|
|
/// and then trigger once the first input arrives. However, <see cref="controls"/> bound to an action may already be
|
|
/// actuated when an action is enabled. For example, if a "jump" action is bound to <see cref="Keyboard.spaceKey"/>,
|
|
/// the space bar may already be pressed when the jump action is enabled.
|
|
///
|
|
/// <see cref="InputActionType.Value"/> actions handle this differently by immediately performing an "initial state check"
|
|
/// in the next input update (see <see cref="InputSystem.Update"/>) after being enabled. If any of the bound controls
|
|
/// is already actuated, the action will trigger right away -- even with no change in state on the controls.
|
|
///
|
|
/// This same behavior can be enabled explicitly for <see cref="InputActionType.Button"/> and <see cref="InputActionType.PassThrough"/>
|
|
/// actions using this property.
|
|
/// </remarks>
|
|
/// <seealso cref="Enable"/>
|
|
/// <seealso cref="InputActionType.Value"/>
|
|
public bool wantsInitialStateCheck
|
|
{
|
|
get => type == InputActionType.Value || (m_Flags & ActionFlags.WantsInitialStateCheck) != 0;
|
|
set
|
|
{
|
|
if (value)
|
|
m_Flags |= ActionFlags.WantsInitialStateCheck;
|
|
else
|
|
m_Flags &= ~ActionFlags.WantsInitialStateCheck;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct an unnamed, free-standing action that is not part of any map or asset
|
|
/// and has no bindings. Bindings can be added with <see
|
|
/// cref="InputActionSetupExtensions.AddBinding(InputAction,string,string,string,string)"/>.
|
|
/// The action type defaults to <see cref="InputActionType.Value"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The action will not have an associated <see cref="InputActionMap"/> and <see cref="actionMap"/>
|
|
/// will thus be <c>null</c>. Use <see cref="InputActionSetupExtensions.AddAction"/> instead if
|
|
/// you want to add a new action to an action map.
|
|
///
|
|
/// The action will remain disabled after construction and thus not listen/react to input yet.
|
|
/// Use <see cref="Enable"/> to enable the action.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Create an action with two bindings.
|
|
/// var action = new InputAction();
|
|
/// action.AddBinding("<Gamepad>/leftStick");
|
|
/// action.AddBinding("<Mouse>/delta");
|
|
///
|
|
/// action.performed += ctx => Debug.Log("Value: " + ctx.ReadValue<Vector2>());
|
|
///
|
|
/// action.Enable();
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
public InputAction()
|
|
{
|
|
m_Id = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct a free-standing action that is not part of an <see cref="InputActionMap"/>.
|
|
/// </summary>
|
|
/// <param name="name">Name of the action. If null or empty, the action will be unnamed.</param>
|
|
/// <param name="type">Type of action to create. Defaults to <see cref="InputActionType.Value"/>, i.e.
|
|
/// an action that provides continuous values.</param>
|
|
/// <param name="binding">If not null or empty, a binding with the given path will be added to the action
|
|
/// right away. The format of the string is the as for <see cref="InputBinding.path"/>.</param>
|
|
/// <param name="interactions">If <paramref name="binding"/> is not null or empty, this parameter represents
|
|
/// the interaction to apply to the newly created binding (i.e. <see cref="InputBinding.interactions"/>). If
|
|
/// <paramref name="binding"/> is not supplied, this parameter represents the interactions to apply to the action
|
|
/// (i.e. the value of <see cref="interactions"/>).</param>
|
|
/// <param name="processors">If <paramref name="binding"/> is not null or empty, this parameter represents
|
|
/// the processors to apply to the newly created binding (i.e. <see cref="InputBinding.processors"/>). If
|
|
/// <paramref name="binding"/> is not supplied, this parameter represents the processors to apply to the
|
|
/// action (i.e. the value of <see cref="processors"/>).</param>
|
|
/// <param name="expectedControlType">The optional expected control type for the action (i.e. <see
|
|
/// cref="expectedControlType"/>).</param>
|
|
/// <remarks>
|
|
/// The action will not have an associated <see cref="InputActionMap"/> and <see cref="actionMap"/>
|
|
/// will thus be <c>null</c>. Use <see cref="InputActionSetupExtensions.AddAction"/> instead if
|
|
/// you want to add a new action to an action map.
|
|
///
|
|
/// The action will remain disabled after construction and thus not listen/react to input yet.
|
|
/// Use <see cref="Enable"/> to enable the action.
|
|
///
|
|
/// Additional bindings can be added with <see
|
|
/// cref="InputActionSetupExtensions.AddBinding(InputAction,string,string,string,string)"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Create a button action responding to the gamepad A button.
|
|
/// var action = new InputAction(type: InputActionType.Button, binding: "<Gamepad>/buttonSouth");
|
|
/// action.performed += ctx => Debug.Log("Pressed");
|
|
/// action.Enable();
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
public InputAction(string name = null, InputActionType type = default, string binding = null,
|
|
string interactions = null, string processors = null, string expectedControlType = null)
|
|
{
|
|
m_Name = name;
|
|
m_Type = type;
|
|
|
|
if (!string.IsNullOrEmpty(binding))
|
|
{
|
|
m_SingletonActionBindings = new[]
|
|
{
|
|
new InputBinding
|
|
{
|
|
path = binding,
|
|
interactions = interactions,
|
|
processors = processors,
|
|
action = m_Name,
|
|
id = Guid.NewGuid(),
|
|
},
|
|
};
|
|
m_BindingsStartIndex = 0;
|
|
m_BindingsCount = 1;
|
|
}
|
|
else
|
|
{
|
|
m_Interactions = interactions;
|
|
m_Processors = processors;
|
|
}
|
|
|
|
m_ExpectedControlType = expectedControlType;
|
|
m_Id = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release internal state held on to by the action.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Once enabled, actions will allocate a block of state internally that they will hold on to
|
|
/// until disposed of. For free-standing actions, that state is private to just the action.
|
|
/// For actions that are part of <see cref="InputActionMap"/>s, the state is shared by all
|
|
/// actions in the map and, if the map itself is part of an <see cref="InputActionAsset"/>,
|
|
/// also by all the maps that are part of the asset.
|
|
///
|
|
/// Note that the internal state holds on to GC heap memory as well as memory from the
|
|
/// unmanaged, C++ heap.
|
|
/// </remarks>
|
|
public void Dispose()
|
|
{
|
|
m_ActionMap?.m_State?.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a string version of the action. Mainly useful for debugging.
|
|
/// </summary>
|
|
/// <returns>A string version of the action.</returns>
|
|
public override string ToString()
|
|
{
|
|
string str;
|
|
if (m_Name == null)
|
|
str = "<Unnamed>";
|
|
else if (m_ActionMap != null && !isSingletonAction && !string.IsNullOrEmpty(m_ActionMap.name))
|
|
str = $"{m_ActionMap.name}/{m_Name}";
|
|
else
|
|
str = m_Name;
|
|
|
|
var controls = this.controls;
|
|
if (controls.Count > 0)
|
|
{
|
|
str += "[";
|
|
var isFirst = true;
|
|
foreach (var control in controls)
|
|
{
|
|
if (!isFirst)
|
|
str += ",";
|
|
str += control.path;
|
|
isFirst = false;
|
|
}
|
|
str += "]";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable the action such that it actively listens for input and runs callbacks
|
|
/// in response.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the action is already enabled, this method does nothing.
|
|
///
|
|
/// By default, actions start out disabled, i.e. with <see cref="enabled"/> being false.
|
|
/// When enabled, two things happen.
|
|
///
|
|
/// First, if it hasn't already happened, an action will resolve all of its bindings
|
|
/// to <see cref="InputControl"/>s. This also happens if, since the action was last enabled,
|
|
/// the setup of devices in the system has changed such that it may impact the action.
|
|
///
|
|
/// Second, for all the <see cref="controls"/> bound to an action, change monitors (see
|
|
/// <see cref="IInputStateChangeMonitor"/>) will be added to the system. If any of the
|
|
/// controls changes state in the future, the action will get notified and respond.
|
|
///
|
|
/// <see cref="InputActionType.Value"/> type actions will also perform an initial state
|
|
/// check in the input system update following the call to Enable. This means that if
|
|
/// any of the bound controls are already actuated and produce a non-<c>default</c> value,
|
|
/// the action will immediately trigger in response.
|
|
///
|
|
/// Note that this method only enables a single action. This is also allowed for action
|
|
/// that are part of an <see cref="InputActionMap"/>. To enable all actions in a map,
|
|
/// call <see cref="InputActionMap.Enable"/>.
|
|
///
|
|
/// The <see cref="InputActionMap"/> associated with an action (if any), will immediately
|
|
/// toggle to being enabled (see <see cref="InputActionMap.enabled"/>) as soon as the first
|
|
/// action in the map is enabled and for as long as any action in the map is still enabled.
|
|
///
|
|
/// The first time an action is enabled, it will allocate a block of state internally that it
|
|
/// will hold on to until disposed of. For free-standing actions, that state is private to
|
|
/// just the action. For actions that are part of <see cref="InputActionMap"/>s, the state
|
|
/// is shared by all actions in the map and, if the map itself is part of an <see
|
|
/// cref="InputActionAsset"/>, also by all the maps that are part of the asset.
|
|
///
|
|
/// To dispose of the state, call <see cref="Dispose"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var gamepad = InputSystem.AddDevice<Gamepad>();
|
|
///
|
|
/// var action = new InputAction(type: InputActionType.Value, binding: "<Gamepad>/leftTrigger");
|
|
/// action.performed = ctx => Debug.Log("Action triggered!");
|
|
///
|
|
/// // Perform some fake input on the gamepad. Note that the action
|
|
/// // will *NOT* get triggered as it is not enabled.
|
|
/// // NOTE: We use Update() here only for demonstration purposes. In most cases,
|
|
/// // it's not a good method to call directly as it basically injects artificial
|
|
/// // input frames into the player loop. Usually a recipe for breakage.
|
|
/// InputSystem.QueueStateEvent(gamepad, new GamepadState { leftTrigger = 0.5f });
|
|
/// InputSystem.Update();
|
|
///
|
|
/// action.Enable();
|
|
///
|
|
/// // Now, with the left trigger already being down and the action enabled, it will
|
|
/// // trigger in the next frame.
|
|
/// InputSystem.Update();
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="Disable"/>
|
|
/// <seealso cref="enabled"/>
|
|
public void Enable()
|
|
{
|
|
if (enabled)
|
|
return;
|
|
|
|
// For singleton actions, we create an internal-only InputActionMap
|
|
// private to the action.
|
|
var map = GetOrCreateActionMap();
|
|
|
|
// First time we're enabled, find all controls.
|
|
map.ResolveBindingsIfNecessary();
|
|
|
|
// Go live.
|
|
map.m_State.EnableSingleAction(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable the action such that is stop listening/responding to input.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the action is already disabled, this method does nothing.
|
|
///
|
|
/// If the action is currently in progress, i.e. if <see cref="phase"/> is
|
|
/// <see cref="InputActionPhase.Started"/>, the action will be canceled as
|
|
/// part of being disabled. This means that you will see a call on <see cref="canceled"/>
|
|
/// from within the call to <c>Disable()</c>.
|
|
/// </remarks>
|
|
/// <seealso cref="enabled"/>
|
|
/// <seealso cref="Enable"/>
|
|
public void Disable()
|
|
{
|
|
if (!enabled)
|
|
return;
|
|
|
|
m_ActionMap.m_State.DisableSingleAction(this);
|
|
}
|
|
|
|
////REVIEW: is *not* cloning IDs here really the right thing to do?
|
|
/// <summary>
|
|
/// Return an identical instance of the action.
|
|
/// </summary>
|
|
/// <returns>An identical clone of the action</returns>
|
|
/// <remarks>
|
|
/// Note that if you clone an action that is part of an <see cref="InputActionMap"/>,
|
|
/// you will not get a new action that is part of the same map. Instead, you will
|
|
/// get a free-standing action not associated with any action map.
|
|
///
|
|
/// Also, note that the <see cref="id"/> of the action is not cloned. Instead, the
|
|
/// clone will receive a new unique ID. Also, callbacks install on events such
|
|
/// as <see cref="started"/> will not be copied over to the clone.
|
|
/// </remarks>
|
|
public InputAction Clone()
|
|
{
|
|
var clone = new InputAction(name: m_Name, type: m_Type)
|
|
{
|
|
m_SingletonActionBindings = bindings.ToArray(),
|
|
m_BindingsCount = m_BindingsCount,
|
|
m_ExpectedControlType = m_ExpectedControlType,
|
|
m_Interactions = m_Interactions,
|
|
m_Processors = m_Processors,
|
|
m_Flags = m_Flags,
|
|
};
|
|
return clone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return an boxed instance of the action.
|
|
/// </summary>
|
|
/// <returns>An boxed clone of the action</returns>
|
|
/// <seealso cref="Clone"/>
|
|
object ICloneable.Clone()
|
|
{
|
|
return Clone();
|
|
}
|
|
|
|
////TODO: ReadValue(void*, int)
|
|
|
|
/// <summary>
|
|
/// Read the current value of the control that is driving this action. If no bound control is actuated, returns
|
|
/// default(TValue), but note that binding processors are always applied.
|
|
/// </summary>
|
|
/// <typeparam name="TValue">Value type to read. Must match the value type of the binding/control that triggered.</typeparam>
|
|
/// <returns>The current value of the control/binding that is driving this action with all binding processors applied.</returns>
|
|
/// <remarks>
|
|
/// This method can be used as an alternative to hooking into <see cref="started"/>, <see cref="performed"/>,
|
|
/// and/or <see cref="canceled"/> and reading out the value using <see cref="CallbackContext.ReadValue{TValue}"/>
|
|
/// there. Instead, this API acts more like a polling API that can be called, for example, as part of
|
|
/// <c>MonoBehaviour.Update</c>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Let's say you have a MyControls.inputactions file with "Generate C# Class" enabled
|
|
/// // and it has an action map called "gameplay" with a "move" action of type Vector2.
|
|
/// public class MyBehavior : MonoBehaviour
|
|
/// {
|
|
/// public MyControls controls;
|
|
/// public float moveSpeed = 4;
|
|
///
|
|
/// protected void Awake()
|
|
/// {
|
|
/// controls = new MyControls();
|
|
/// }
|
|
///
|
|
/// protected void OnEnable()
|
|
/// {
|
|
/// controls.gameplay.Enable();
|
|
/// }
|
|
///
|
|
/// protected void OnDisable()
|
|
/// {
|
|
/// controls.gameplay.Disable();
|
|
/// }
|
|
///
|
|
/// protected void Update()
|
|
/// {
|
|
/// var moveVector = controls.gameplay.move.ReadValue<Vector2>() * (moveSpeed * Time.deltaTime);
|
|
/// //...
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// If the action has button-like behavior, then <see cref="triggered"/> is usually a better alternative to
|
|
/// reading out a float and checking if it is above the button press point.
|
|
/// </remarks>
|
|
/// <exception cref="InvalidOperationException">The given <typeparamref name="TValue"/> type does not match
|
|
/// the value type of the control or composite currently driving the action.</exception>
|
|
/// <seealso cref="triggered"/>
|
|
/// <seealso cref="ReadValueAsObject"/>
|
|
/// <seealso cref="CallbackContext.ReadValue{TValue}"/>
|
|
public unsafe TValue ReadValue<TValue>()
|
|
where TValue : struct
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state == null) return default(TValue);
|
|
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
return actionStatePtr->phase.IsInProgress()
|
|
? state.ReadValue<TValue>(actionStatePtr->bindingIndex, actionStatePtr->controlIndex)
|
|
: state.ApplyProcessors(actionStatePtr->bindingIndex, default(TValue));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Same as <see cref="ReadValue{TValue}"/> but read the value without having to know the value type
|
|
/// of the action.
|
|
/// </summary>
|
|
/// <returns>The current value of the action or <c>null</c> if the action is not currently in <see cref="InputActionPhase.Started"/>
|
|
/// or <see cref="InputActionPhase.Performed"/> phase.</returns>
|
|
/// <remarks>
|
|
/// This method allocates GC memory and is thus not a good choice for getting called as part of gameplay
|
|
/// logic.
|
|
/// </remarks>
|
|
/// <seealso cref="ReadValue{TValue}"/>
|
|
/// <seealso cref="InputAction.CallbackContext.ReadValueAsObject"/>
|
|
public unsafe object ReadValueAsObject()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state == null)
|
|
return null;
|
|
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
if (actionStatePtr->phase.IsInProgress())
|
|
{
|
|
var controlIndex = actionStatePtr->controlIndex;
|
|
if (controlIndex != InputActionState.kInvalidIndex)
|
|
return state.ReadValueAsObject(actionStatePtr->bindingIndex, controlIndex);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the action state to default.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method can be used to forcibly cancel an action even while it is in progress. Note that unlike
|
|
/// disabling an action, for example, this also effects APIs such as <see cref="WasPressedThisFrame"/>.
|
|
///
|
|
/// Note that invoking this method will not modify enabled state.
|
|
/// </remarks>
|
|
/// <seealso cref="inProgress"/>
|
|
/// <seealso cref="phase"/>
|
|
/// <seealso cref="Enable"/>
|
|
/// <seealso cref="Disable"/>
|
|
public void Reset()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
state?.ResetActionState(m_ActionIndexInState, toPhase: enabled ? InputActionPhase.Waiting : InputActionPhase.Disabled, hardReset: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check whether the current actuation of the action has crossed the button press threshold (see
|
|
/// <see cref="InputSettings.defaultButtonPressPoint"/>) and has not yet fallen back below the
|
|
/// release threshold (see <see cref="InputSettings.buttonReleaseThreshold"/>).
|
|
/// </summary>
|
|
/// <returns>True if the action is considered to be in "pressed" state, false otherwise.</returns>
|
|
/// <remarks>
|
|
/// This method is different from simply reading the action's current <c>float</c> value and comparing
|
|
/// it to the press threshold and is also different from comparing the current actuation of
|
|
/// <see cref="activeControl"/> to it. This is because the current level of actuation might have already
|
|
/// fallen below the press threshold but might not yet have reached the release threshold.
|
|
///
|
|
/// This method works with any <see cref="type"/> of action, not just buttons.
|
|
///
|
|
/// Also note that because this operates on the results of <see cref="InputControl.EvaluateMagnitude()"/>,
|
|
/// it works with many kind of controls, not just buttons. For example, if an action is bound
|
|
/// to a <see cref="StickControl"/>, the control will be considered "pressed" once the magnitude
|
|
/// of the Vector2 of the control has crossed the press threshold.
|
|
///
|
|
/// Finally, note that custom button press points of controls (see <see cref="ButtonControl.pressPoint"/>)
|
|
/// are respected and will take precedence over <see cref="InputSettings.defaultButtonPressPoint"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var up = playerInput.actions["up"];
|
|
/// if (up.IsPressed())
|
|
/// transform.Translate(0, 10 * Time.deltaTime, 0);
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// Disabled actions will always return false from this method, even if a control bound to the action
|
|
/// is currently pressed. Also, re-enabling an action will not restore the state to when the action
|
|
/// was disabled even if the control is still actuated.
|
|
/// </remarks>
|
|
/// <seealso cref="InputSettings.defaultButtonPressPoint"/>
|
|
/// <seealso cref="ButtonControl.pressPoint"/>
|
|
/// <seealso cref="CallbackContext.ReadValueAsButton"/>
|
|
/// <seealso cref="WasPressedThisFrame"/>
|
|
/// <seealso cref="WasReleasedThisFrame"/>
|
|
public unsafe bool IsPressed()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
return actionStatePtr->isPressed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the action has been <see cref="InputActionPhase.Started"/> or <see cref="InputActionPhase.Performed"/>.
|
|
/// </summary>
|
|
/// <returns>True if the action is currently triggering.</returns>
|
|
/// <seealso cref="phase"/>
|
|
public unsafe bool IsInProgress()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
return actionStatePtr->phase.IsInProgress();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the action's value crossed the press threshold (see <see cref="InputSettings.defaultButtonPressPoint"/>)
|
|
/// at any point in the frame.
|
|
/// </summary>
|
|
/// <returns>True if the action was pressed this frame.</returns>
|
|
/// <remarks>
|
|
/// This method is different from <see cref="WasPerformedThisFrame"/> in that it is not bound
|
|
/// to <see cref="phase"/>. Instead, if the action's level of actuation (that is, the level of
|
|
/// magnitude -- see <see cref="InputControl.EvaluateMagnitude()"/> -- of the control(s) bound
|
|
/// to the action) crossed the press threshold (see <see cref="InputSettings.defaultButtonPressPoint"/>)
|
|
/// at any point in the frame, this method will return true. It will do so even if there is an
|
|
/// interaction on the action that has not yet performed the action in response to the press.
|
|
///
|
|
/// This method works with any <see cref="type"/> of action, not just buttons.
|
|
///
|
|
/// Also note that because this operates on the results of <see cref="InputControl.EvaluateMagnitude()"/>,
|
|
/// it works with many kind of controls, not just buttons. For example, if an action is bound
|
|
/// to a <see cref="StickControl"/>, the control will be considered "pressed" once the magnitude
|
|
/// of the Vector2 of the control has crossed the press threshold.
|
|
///
|
|
/// Finally, note that custom button press points of controls (see <see cref="ButtonControl.pressPoint"/>)
|
|
/// are respected and will take precedence over <see cref="InputSettings.defaultButtonPressPoint"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var fire = playerInput.actions["fire"];
|
|
/// if (fire.WasPressedThisFrame() && fire.IsPressed())
|
|
/// StartFiring();
|
|
/// else if (fire.WasReleasedThisFrame())
|
|
/// StopFiring();
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// This method will disregard whether the action is currently enabled or disabled. It will keep returning
|
|
/// true for the duration of the frame even if the action was subsequently disabled in the frame.
|
|
///
|
|
/// The meaning of "frame" is either the current "dynamic" update (<c>MonoBehaviour.Update</c>) or the current
|
|
/// fixed update (<c>MonoBehaviour.FixedUpdate</c>) depending on the value of the <see cref="InputSettings.updateMode"/> setting.
|
|
/// </remarks>
|
|
/// <seealso cref="IsPressed"/>
|
|
/// <seealso cref="WasReleasedThisFrame"/>
|
|
/// <seealso cref="CallbackContext.ReadValueAsButton"/>
|
|
/// <seealso cref="WasPerformedThisFrame"/>
|
|
public unsafe bool WasPressedThisFrame()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
var currentUpdateStep = InputUpdate.s_UpdateStepCount;
|
|
return actionStatePtr->pressedInUpdate == currentUpdateStep && currentUpdateStep != default;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the action's value crossed the release threshold (see <see cref="InputSettings.buttonReleaseThreshold"/>)
|
|
/// at any point in the frame after being in pressed state.
|
|
/// </summary>
|
|
/// <returns>True if the action was released this frame.</returns>
|
|
/// <remarks>
|
|
/// This method works with any <see cref="type"/> of action, not just buttons.
|
|
///
|
|
/// Also note that because this operates on the results of <see cref="InputControl.EvaluateMagnitude()"/>,
|
|
/// it works with many kind of controls, not just buttons. For example, if an action is bound
|
|
/// to a <see cref="StickControl"/>, the control will be considered "pressed" once the magnitude
|
|
/// of the Vector2 of the control has crossed the press threshold.
|
|
///
|
|
/// Finally, note that custom button press points of controls (see <see cref="ButtonControl.pressPoint"/>)
|
|
/// are respected and will take precedence over <see cref="InputSettings.defaultButtonPressPoint"/>.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var fire = playerInput.actions["fire"];
|
|
/// if (fire.WasPressedThisFrame() && fire.IsPressed())
|
|
/// StartFiring();
|
|
/// else if (fire.WasReleasedThisFrame())
|
|
/// StopFiring();
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// This method will disregard whether the action is currently enabled or disabled. It will keep returning
|
|
/// true for the duration of the frame even if the action was subsequently disabled in the frame.
|
|
///
|
|
/// The meaning of "frame" is either the current "dynamic" update (<c>MonoBehaviour.Update</c>) or the current
|
|
/// fixed update (<c>MonoBehaviour.FixedUpdate</c>) depending on the value of the <see cref="InputSettings.updateMode"/> setting.
|
|
/// </remarks>
|
|
/// <seealso cref="IsPressed"/>
|
|
/// <seealso cref="WasPressedThisFrame"/>
|
|
/// <seealso cref="CallbackContext.ReadValueAsButton"/>
|
|
/// <seealso cref="WasPerformedThisFrame"/>
|
|
public unsafe bool WasReleasedThisFrame()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
var currentUpdateStep = InputUpdate.s_UpdateStepCount;
|
|
return actionStatePtr->releasedInUpdate == currentUpdateStep && currentUpdateStep != default;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////REVIEW: Should we also have WasStartedThisFrame()? (and WasCanceledThisFrame()?)
|
|
|
|
/// <summary>
|
|
/// Check whether <see cref="phase"/> was <see cref="InputActionPhase.Performed"/> at any point
|
|
/// in the current frame.
|
|
/// </summary>
|
|
/// <returns>True if the action performed this frame.</returns>
|
|
/// <remarks>
|
|
/// This method is different from <see cref="WasPressedThisFrame"/> in that it 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).
|
|
///
|
|
/// For example, let's say the action is bound to the space bar and that the binding has a
|
|
/// <see cref="Interactions.HoldInteraction"/> assigned to it. In the frame where the space bar
|
|
/// is pressed, <see cref="WasPressedThisFrame"/> will be true (because the button/key is now pressed)
|
|
/// but <c>WasPerformedThisFrame</c> will still be false (because the hold has not been performed yet).
|
|
/// Only after the hold time has expired will <c>WasPerformedThisFrame</c> be true and only in the frame
|
|
/// where the hold performed.
|
|
///
|
|
/// This is different from checking <see cref="phase"/> directly as the action might have already progressed
|
|
/// to a different phase after performing. In other words, even if an action performed in a frame, <see cref="phase"/>
|
|
/// might no longer be <see cref="InputActionPhase.Performed"/>, whereas <c>WasPerformedThisFrame</c> will remain
|
|
/// true for the entirety of the frame regardless of what else the action does.
|
|
///
|
|
/// Unlike <see cref="ReadValue{TValue}"/>, which will reset when the action goes back to waiting
|
|
/// state, this property will stay true for the duration of the current frame (that is, until the next
|
|
/// <see cref="InputSystem.Update"/> runs) as long as the action was triggered at least once.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// var warp = playerInput.actions["Warp"];
|
|
/// if (warp.WasPerformedThisFrame())
|
|
/// InitiateWarp();
|
|
/// </code>
|
|
/// </example>
|
|
///
|
|
/// This method will disregard whether the action is currently enabled or disabled. It will keep returning
|
|
/// true for the duration of the frame even if the action was subsequently disabled in the frame.
|
|
///
|
|
/// The meaning of "frame" is either the current "dynamic" update (<c>MonoBehaviour.Update</c>) or the current
|
|
/// fixed update (<c>MonoBehaviour.FixedUpdate</c>) depending on the value of the <see cref="InputSettings.updateMode"/> setting.
|
|
/// </remarks>
|
|
/// <seealso cref="WasPressedThisFrame"/>
|
|
/// <seealso cref="phase"/>
|
|
public unsafe bool WasPerformedThisFrame()
|
|
{
|
|
var state = GetOrCreateActionMap().m_State;
|
|
|
|
if (state != null)
|
|
{
|
|
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
|
|
var currentUpdateStep = InputUpdate.s_UpdateStepCount;
|
|
return actionStatePtr->lastPerformedInUpdate == currentUpdateStep && currentUpdateStep != default;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the completion percentage of the timeout (if any) running on the current interaction.
|
|
/// </summary>
|
|
/// <returns>A value >= 0 (no progress) and <= 1 (finished) indicating the level of completion
|
|
/// of the currently running timeout.</returns>
|
|
/// <remarks>
|
|
/// This method is useful, for example, when providing UI feedback for an ongoing action. If, say,
|
|
/// you have a <see cref="Interactions.HoldInteraction"/> on a binding, you might want to show a
|
|
/// progress indicator in the UI and need to know how far into the hold the action
|
|
/// current is. Once the hold has been started, this method will return how far into the hold
|
|
/// the action currently is.
|
|
///
|
|
/// Note that if an interaction performs and stays performed (see <see cref="InputInteractionContext.PerformedAndStayPerformed"/>),
|
|
/// the completion percentage will remain at 1 until the interaction is canceled.
|
|
///
|
|
/// Also note that completion is based on the progression of time and not dependent on input
|
|
/// updates. This means that if, for example, the timeout for a <see cref="Interactions.HoldInteraction"/>
|
|
/// has expired according the current time but the expiration has not yet been processed by
|
|
/// an input update (thus causing the hold to perform), the returned completion percentage
|
|
/// will still be 1. In other words, there isn't always a correlation between the current
|
|
/// completion percentage and <see cref="phase"/>.
|
|
///
|
|
/// The meaning of the timeout is dependent on the interaction in play. For a <see cref="Interactions.HoldInteraction"/>,
|
|
/// the timeout represents "completion" (that is, the time until a "hold" is considered to be performed), whereas
|
|
/// for a <see cref="Interactions.TapInteraction"/> it represents "time to failure" (that is, the remaining time window
|
|
/// that the interaction can be completed within).
|
|
///
|
|
/// Note that an interaction might run multiple timeouts in succession. One such example is <see cref="Interactions.MultiTapInteraction"/>.
|
|
/// In this case, progression towards a single timeout does not necessarily mean progression towards completion
|
|
/// of the whole interaction. An interaction can call <see cref="InputInteractionContext.SetTotalTimeoutCompletionTime"/>
|
|
/// to inform the Input System of the total length of timeouts to run. If this is done, the result of the
|
|
/// <c>GetTimeoutCompletionPercentage</c> method will return a value reflecting the progression with respect
|
|
/// to total time.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Scale a UI element in response to the completion of a hold on the gamepad's A button.
|
|
///
|
|
/// Transform uiObjectToScale;
|
|
///
|
|
/// InputAction holdAction;
|
|
///
|
|
/// void OnEnable()
|
|
/// {
|
|
/// if (holdAction == null)
|
|
/// {
|
|
/// // Create hold action with a 2 second timeout.
|
|
/// // NOTE: Here we create the action in code. You can, of course, grab the action from an .inputactions
|
|
/// // asset created in the editor instead.
|
|
/// holdAction = new InputAction(type: InputActionType.Button, interactions: "hold(duration=2)");
|
|
///
|
|
/// // Show the UI object when the hold starts and hide it when it ends.
|
|
/// holdAction.started += _ => uiObjectToScale.SetActive(true);
|
|
/// holdAction.canceled += _ => uiObjectToScale.SetActive(false);
|
|
///
|
|
/// // If you want to play a visual effect when the action performs, you can initiate from
|
|
/// // the performed callback.
|
|
/// holdAction.performed += _ => /* InitiateVisualEffectWhenHoldIsComplete() */;
|
|
/// }
|
|
///
|
|
/// holdAction.Enable();
|
|
///
|
|
/// // Hide the UI object until the action is started.
|
|
/// uiObjectToScale.gameObject.SetActive(false);
|
|
/// }
|
|
///
|
|
/// void OnDisable()
|
|
/// {
|
|
/// holdAction.Disable();
|
|
/// }
|
|
///
|
|
/// void Update()
|
|
/// {
|
|
/// var completion = holdAction.GetTimeoutCompletionPercentage();
|
|
/// uiObjectToScale.localScale = new Vector3(1, completion, 1);
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="IInputInteraction"/>
|
|
/// <seealso cref="InputInteractionContext.SetTimeout"/>
|
|
/// <seealso cref="InputInteractionContext.SetTotalTimeoutCompletionTime"/>
|
|
public unsafe float GetTimeoutCompletionPercentage()
|
|
{
|
|
var actionMap = GetOrCreateActionMap();
|
|
var state = actionMap.m_State;
|
|
|
|
// If there's no state, there can't be activity on the action so our completion
|
|
// percentage must be zero.
|
|
if (state == null)
|
|
return 0;
|
|
|
|
ref var actionState = ref state.actionStates[m_ActionIndexInState];
|
|
var interactionIndex = actionState.interactionIndex;
|
|
if (interactionIndex == -1)
|
|
{
|
|
////REVIEW: should this use WasPerformedThisFrame()?
|
|
// There's no interactions on the action or on the currently active binding, so go
|
|
// entirely by the current phase. Performed is 100%, everything else is 0%.
|
|
return actionState.phase == InputActionPhase.Performed ? 1 : 0;
|
|
}
|
|
|
|
ref var interactionState = ref state.interactionStates[interactionIndex];
|
|
switch (interactionState.phase)
|
|
{
|
|
case InputActionPhase.Started:
|
|
// If the interaction was started and there is a timer running, the completion level
|
|
// is determined by far we are between the interaction start time and timer expiration.
|
|
var timerCompletion = 0f;
|
|
if (interactionState.isTimerRunning)
|
|
{
|
|
var duration = interactionState.timerDuration;
|
|
var startTime = interactionState.timerStartTime;
|
|
var endTime = startTime + duration;
|
|
var remainingTime = endTime - InputState.currentTime;
|
|
if (remainingTime <= 0)
|
|
timerCompletion = 1;
|
|
else
|
|
timerCompletion = (float)((duration - remainingTime) / duration);
|
|
}
|
|
|
|
if (interactionState.totalTimeoutCompletionTimeRemaining > 0)
|
|
{
|
|
return (interactionState.totalTimeoutCompletionDone + timerCompletion * interactionState.timerDuration) /
|
|
(interactionState.totalTimeoutCompletionDone + interactionState.totalTimeoutCompletionTimeRemaining);
|
|
}
|
|
else
|
|
{
|
|
return timerCompletion;
|
|
}
|
|
|
|
case InputActionPhase.Performed:
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
////REVIEW: it would be best if these were InternedStrings; however, for serialization, it has to be strings
|
|
[Tooltip("Human readable name of the action. Must be unique within its action map (case is ignored). Can be changed "
|
|
+ "without breaking references to the action.")]
|
|
[SerializeField] internal string m_Name;
|
|
[Tooltip("Determines how the action triggers.\n"
|
|
+ "\n"
|
|
+ "A Value action will start and perform when a control moves from its default value and then "
|
|
+ "perform on every value change. It will cancel when controls go back to default value. Also, when enabled, a Value "
|
|
+ "action will respond right away to a control's current value.\n"
|
|
+ "\n"
|
|
+ "A Button action will start when a button is pressed and perform when the press threshold (see 'Default Button Press Point' in settings) "
|
|
+ "is reached. It will cancel when the button is going below the release threshold (see 'Button Release Threshold' in settings). Also, "
|
|
+ "if a button is already pressed when the action is enabled, the button has to be released first.\n"
|
|
+ "\n"
|
|
+ "A Pass-Through action will not explicitly start and will never cancel. Instead, for every value change on any bound control, "
|
|
+ "the action will perform.")]
|
|
[SerializeField] internal InputActionType m_Type;
|
|
[FormerlySerializedAs("m_ExpectedControlLayout")]
|
|
[Tooltip("The type of control expected by the action (e.g. \"Button\" or \"Stick\"). This will limit the controls shown "
|
|
+ "when setting up bindings in the UI and will also limit which controls can be bound interactively to the action.")]
|
|
[SerializeField] internal string m_ExpectedControlType;
|
|
[Tooltip("Unique ID of the action (GUID). Used to reference the action from bindings such that actions can be renamed "
|
|
+ "without breaking references.")]
|
|
[SerializeField] internal string m_Id; // Can't serialize System.Guid and Unity's GUID is editor only.
|
|
[SerializeField] internal string m_Processors;
|
|
[SerializeField] internal string m_Interactions;
|
|
|
|
// For singleton actions, we serialize the bindings directly as part of the action.
|
|
// For any other type of action, this is null.
|
|
[SerializeField] internal InputBinding[] m_SingletonActionBindings;
|
|
[SerializeField] internal ActionFlags m_Flags;
|
|
|
|
[NonSerialized] internal InputBinding? m_BindingMask;
|
|
[NonSerialized] internal int m_BindingsStartIndex;
|
|
[NonSerialized] internal int m_BindingsCount;
|
|
[NonSerialized] internal int m_ControlStartIndex;
|
|
[NonSerialized] internal int m_ControlCount;
|
|
|
|
/// <summary>
|
|
/// Index of the action in the <see cref="InputActionState"/> associated with the
|
|
/// action's <see cref="InputActionMap"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is not necessarily the same as the index of the action in its map.
|
|
/// </remarks>
|
|
/// <seealso cref="actionMap"/>
|
|
[NonSerialized] internal int m_ActionIndexInState = InputActionState.kInvalidIndex;
|
|
|
|
/// <summary>
|
|
/// The action map that owns the action.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is not serialized. The action map will restore this back references after deserialization.
|
|
/// </remarks>
|
|
[NonSerialized] internal InputActionMap m_ActionMap;
|
|
|
|
// Listeners. No array allocations if only a single listener.
|
|
[NonSerialized] internal CallbackArray<Action<CallbackContext>> m_OnStarted;
|
|
[NonSerialized] internal CallbackArray<Action<CallbackContext>> m_OnCanceled;
|
|
[NonSerialized] internal CallbackArray<Action<CallbackContext>> m_OnPerformed;
|
|
|
|
/// <summary>
|
|
/// Whether the action is a loose action created in code (e.g. as a property on a component).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Singleton actions are not contained in maps visible to the user. Internally, we do create
|
|
/// a map for them that contains just the singleton action. To the action system, there are no
|
|
/// actions without action maps.
|
|
/// </remarks>
|
|
internal bool isSingletonAction => m_ActionMap == null || ReferenceEquals(m_ActionMap.m_SingletonAction, this);
|
|
|
|
[Flags]
|
|
internal enum ActionFlags
|
|
{
|
|
WantsInitialStateCheck = 1 << 0,
|
|
}
|
|
|
|
private InputActionState.TriggerState currentState
|
|
{
|
|
get
|
|
{
|
|
if (m_ActionIndexInState == InputActionState.kInvalidIndex)
|
|
return new InputActionState.TriggerState();
|
|
Debug.Assert(m_ActionMap != null, "Action must have associated action map");
|
|
Debug.Assert(m_ActionMap.m_State != null, "Action map must have state at this point");
|
|
return m_ActionMap.m_State.FetchActionState(this);
|
|
}
|
|
}
|
|
|
|
internal string MakeSureIdIsInPlace()
|
|
{
|
|
if (string.IsNullOrEmpty(m_Id))
|
|
GenerateId();
|
|
return m_Id;
|
|
}
|
|
|
|
internal void GenerateId()
|
|
{
|
|
m_Id = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
internal InputActionMap GetOrCreateActionMap()
|
|
{
|
|
if (m_ActionMap == null)
|
|
CreateInternalActionMapForSingletonAction();
|
|
return m_ActionMap;
|
|
}
|
|
|
|
private void CreateInternalActionMapForSingletonAction()
|
|
{
|
|
m_ActionMap = new InputActionMap
|
|
{
|
|
m_Actions = new[] { this },
|
|
m_SingletonAction = this,
|
|
m_Bindings = m_SingletonActionBindings
|
|
};
|
|
}
|
|
|
|
internal void RequestInitialStateCheckOnEnabledAction()
|
|
{
|
|
Debug.Assert(enabled, "This should only be called on actions that are enabled");
|
|
|
|
var map = GetOrCreateActionMap();
|
|
var state = map.m_State;
|
|
state.SetInitialStateCheckPending(m_ActionIndexInState);
|
|
}
|
|
|
|
// NOTE: This does *NOT* check whether the control is valid according to the binding it
|
|
// resolved from and/or the current binding mask. If, for example, the binding is
|
|
// "<Keyboard>/#(ä)" and the keyboard switches from a DE layout to a US layout, the
|
|
// key would still be considered valid even if the path in the binding would actually
|
|
// no longer resolve to it.
|
|
internal bool ActiveControlIsValid(InputControl control)
|
|
{
|
|
if (control == null)
|
|
return false;
|
|
|
|
// Device must still be added.
|
|
var device = control.device;
|
|
if (!device.added)
|
|
return false;
|
|
|
|
// If we have a device list in the map or asset, device
|
|
// must be in list.
|
|
var map = GetOrCreateActionMap();
|
|
var deviceList = map.devices;
|
|
if (deviceList != null && !deviceList.Value.ContainsReference(device))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
internal InputBinding? FindEffectiveBindingMask()
|
|
{
|
|
if (m_BindingMask.HasValue)
|
|
return m_BindingMask;
|
|
|
|
if (m_ActionMap?.m_BindingMask != null)
|
|
return m_ActionMap.m_BindingMask;
|
|
|
|
return m_ActionMap?.m_Asset?.m_BindingMask;
|
|
}
|
|
|
|
internal int BindingIndexOnActionToBindingIndexOnMap(int indexOfBindingOnAction)
|
|
{
|
|
// We don't want to hit InputAction.bindings here as this requires setting up per-action
|
|
// binding info which we then nuke as part of the override process. Calling ApplyBindingOverride
|
|
// repeatedly with an index would thus cause the same data to be computed and thrown away
|
|
// over and over.
|
|
// Instead we manually search through the map's bindings to find the right binding index
|
|
// in the map.
|
|
|
|
var actionMap = GetOrCreateActionMap();
|
|
var bindingsInMap = actionMap.m_Bindings;
|
|
var bindingCountInMap = bindingsInMap.LengthSafe();
|
|
var actionName = name;
|
|
|
|
var currentBindingIndexOnAction = -1;
|
|
for (var i = 0; i < bindingCountInMap; ++i)
|
|
{
|
|
ref var binding = ref bindingsInMap[i];
|
|
|
|
if (!binding.TriggersAction(this))
|
|
continue;
|
|
|
|
++currentBindingIndexOnAction;
|
|
if (currentBindingIndexOnAction == indexOfBindingOnAction)
|
|
return i;
|
|
}
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(indexOfBindingOnAction),
|
|
$"Binding index {indexOfBindingOnAction} is out of range for action '{this}' with {currentBindingIndexOnAction + 1} bindings");
|
|
}
|
|
|
|
internal int BindingIndexOnMapToBindingIndexOnAction(int indexOfBindingOnMap)
|
|
{
|
|
var actionMap = GetOrCreateActionMap();
|
|
var bindingsInMap = actionMap.m_Bindings;
|
|
var actionName = name;
|
|
|
|
var bindingIndexOnAction = 0;
|
|
for (var i = indexOfBindingOnMap - 1; i >= 0; --i)
|
|
{
|
|
ref var binding = ref bindingsInMap[i];
|
|
|
|
if (string.Compare(binding.action, actionName, StringComparison.InvariantCultureIgnoreCase) == 0 ||
|
|
binding.action == m_Id)
|
|
++bindingIndexOnAction;
|
|
}
|
|
|
|
return bindingIndexOnAction;
|
|
}
|
|
|
|
////TODO: make current event available in some form
|
|
////TODO: make source binding info available (binding index? binding instance?)
|
|
|
|
/// <summary>
|
|
/// Information provided to action callbacks about what triggered an action.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This struct should not be held on to past the duration of the callback.
|
|
/// </remarks>
|
|
/// <seealso cref="performed"/>
|
|
/// <seealso cref="started"/>
|
|
/// <seealso cref="canceled"/>
|
|
/// <seealso cref="InputActionMap.actionTriggered"/>
|
|
public struct CallbackContext // Ideally would be a ref struct but couldn't use it in lambdas then.
|
|
{
|
|
internal InputActionState m_State;
|
|
internal int m_ActionIndex;
|
|
|
|
////REVIEW: there should probably be a mechanism for the user to be able to correlate
|
|
//// the callback to a specific binding on the action
|
|
|
|
private int actionIndex => m_ActionIndex;
|
|
private unsafe int bindingIndex => m_State.actionStates[actionIndex].bindingIndex;
|
|
private unsafe int controlIndex => m_State.actionStates[actionIndex].controlIndex;
|
|
private unsafe int interactionIndex => m_State.actionStates[actionIndex].interactionIndex;
|
|
|
|
/// <summary>
|
|
/// Current phase of the action. Equivalent to accessing <see cref="InputAction.phase"/>
|
|
/// on <see cref="action"/>.
|
|
/// </summary>
|
|
/// <value>Current phase of the action.</value>
|
|
/// <seealso cref="started"/>
|
|
/// <seealso cref="performed"/>
|
|
/// <seealso cref="canceled"/>
|
|
/// <seealso cref="InputAction.phase"/>
|
|
public unsafe InputActionPhase phase
|
|
{
|
|
get
|
|
{
|
|
if (m_State == null)
|
|
return InputActionPhase.Disabled;
|
|
return m_State.actionStates[actionIndex].phase;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the <see cref="action"/> has just been started.
|
|
/// </summary>
|
|
/// <value>If true, the action was just started.</value>
|
|
/// <seealso cref="InputAction.started"/>
|
|
public bool started => phase == InputActionPhase.Started;
|
|
|
|
/// <summary>
|
|
/// Whether the <see cref="action"/> has just been performed.
|
|
/// </summary>
|
|
/// <value>If true, the action was just performed.</value>
|
|
/// <seealso cref="InputAction.performed"/>
|
|
public bool performed => phase == InputActionPhase.Performed;
|
|
|
|
/// <summary>
|
|
/// Whether the <see cref="action"/> has just been canceled.
|
|
/// </summary>
|
|
/// <value>If true, the action was just canceled.</value>
|
|
/// <seealso cref="InputAction.canceled"/>
|
|
public bool canceled => phase == InputActionPhase.Canceled;
|
|
|
|
/// <summary>
|
|
/// The action that got triggered.
|
|
/// </summary>
|
|
/// <value>Action that got triggered.</value>
|
|
public InputAction action => m_State?.GetActionOrNull(bindingIndex);
|
|
|
|
/// <summary>
|
|
/// The control that triggered the action.
|
|
/// </summary>
|
|
/// <value>Control that triggered the action.</value>
|
|
/// <remarks>
|
|
/// In case of a composite binding, this is the control of the composite that activated the
|
|
/// composite as a whole. For example, in case of a WASD-style binding, it could be the W key.
|
|
///
|
|
/// Note that an action may also change its <see cref="phase"/> in response to a timeout.
|
|
/// For example, a <see cref="Interactions.TapInteraction"/> will cancel itself if the
|
|
/// button control is not released within a certain time. When this happens, the <c>control</c>
|
|
/// property will be the control that last fed input into the action.
|
|
/// </remarks>
|
|
/// <seealso cref="InputAction.controls"/>
|
|
/// <seealso cref="InputBinding.path"/>
|
|
public InputControl control => m_State?.controls[controlIndex];
|
|
|
|
/// <summary>
|
|
/// The interaction that triggered the action or <c>null</c> if the binding that triggered does not
|
|
/// have any particular interaction set on it.
|
|
/// </summary>
|
|
/// <value>Interaction that triggered the callback.</value>
|
|
/// <remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// void FirePerformed(InputAction.CallbackContext context)
|
|
/// {
|
|
/// // If SlowTap interaction was performed, perform a charged
|
|
/// // firing. Otherwise, fire normally.
|
|
/// if (context.interaction is SlowTapInteraction)
|
|
/// FireChargedProjectile();
|
|
/// else
|
|
/// FireNormalProjectile();
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputBinding.interactions"/>
|
|
/// <seealso cref="InputAction.interactions"/>
|
|
public IInputInteraction interaction
|
|
{
|
|
get
|
|
{
|
|
if (m_State == null)
|
|
return null;
|
|
var index = interactionIndex;
|
|
if (index == InputActionState.kInvalidIndex)
|
|
return null;
|
|
return m_State.interactions[index];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The time at which the action got triggered.
|
|
/// </summary>
|
|
/// <value>Time relative to <c>Time.realtimeSinceStartup</c> at which
|
|
/// the action got triggered.</value>
|
|
/// <remarks>
|
|
/// This is usually determined by the timestamp of the input event that activated a control
|
|
/// bound to the action. What this means is that this is normally <em>not</em> the
|
|
/// value of <c>Time.realtimeSinceStartup</c> when the input system calls the
|
|
/// callback but rather the time at which the input was generated that triggered
|
|
/// the action.
|
|
/// </remarks>
|
|
/// <seealso cref="InputEvent.time"/>
|
|
public unsafe double time
|
|
{
|
|
get
|
|
{
|
|
if (m_State == null)
|
|
return 0;
|
|
return m_State.actionStates[actionIndex].time;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Time at which the action was started.
|
|
/// </summary>
|
|
/// <value>Value relative to <c>Time.realtimeSinceStartup</c> when the action
|
|
/// changed to <see cref="started"/>.</value>
|
|
/// <remarks>
|
|
/// This is only relevant for actions that go through distinct a <see cref="InputActionPhase.Started"/>
|
|
/// cycle as driven by <see cref="IInputInteraction">interactions</see>.
|
|
///
|
|
/// The value of this property is that of <see cref="time"/> when <see
|
|
/// cref="InputAction.started"/> was called. See the <see cref="time"/>
|
|
/// property for how the timestamp works.
|
|
/// </remarks>
|
|
public unsafe double startTime
|
|
{
|
|
get
|
|
{
|
|
if (m_State == null)
|
|
return 0;
|
|
return m_State.actionStates[actionIndex].startTime;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Time difference between <see cref="time"/> and <see cref="startTime"/>.
|
|
/// </summary>
|
|
/// <value>Difference between <see cref="time"/> and <see cref="startTime"/>.</value>
|
|
/// <remarks>
|
|
/// This property can be used, for example, to determine how long a button
|
|
/// was held down.
|
|
///
|
|
/// <example>
|
|
/// <code>
|
|
/// // Let's create a button action bound to the A button
|
|
/// // on the gamepad.
|
|
/// var action = new InputAction(
|
|
/// type: InputActionType.Button,
|
|
/// binding: "<Gamepad>/buttonSouth");
|
|
///
|
|
/// // When the action is performed (which will happen when the
|
|
/// // button is pressed and then released) we take the duration
|
|
/// // of the press to determine how many projectiles to spawn.
|
|
/// action.performed +=
|
|
/// context =>
|
|
/// {
|
|
/// const float kSpawnRate = 3; // 3 projectiles per second
|
|
/// var projectileCount = kSpawnRate * context.duration;
|
|
/// for (var i = 0; i < projectileCount; ++i)
|
|
/// {
|
|
/// var projectile = UnityEngine.Object.Instantiate(projectile);
|
|
/// // Apply other changes to the projectile...
|
|
/// }
|
|
/// };
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
public double duration => time - startTime;
|
|
|
|
/// <summary>
|
|
/// Type of value returned by <see cref="ReadValueAsObject"/> and expected
|
|
/// by <see cref="ReadValue{TValue}"/>.
|
|
/// </summary>
|
|
/// <value>Type of object returned when reading a value.</value>
|
|
/// <remarks>
|
|
/// The type of value returned by an action is usually determined by the
|
|
/// <see cref="InputControl"/> that triggered the action, i.e. by the
|
|
/// control referenced from <see cref="control"/>.
|
|
///
|
|
/// However, if the binding that triggered is a composite, then the composite
|
|
/// will determine values and not the individual control that triggered (that
|
|
/// one just feeds values into the composite).
|
|
/// </remarks>
|
|
/// <seealso cref="InputControl.valueType"/>
|
|
/// <seealso cref="InputBindingComposite.valueType"/>
|
|
public Type valueType => m_State?.GetValueType(bindingIndex, controlIndex);
|
|
|
|
/// <summary>
|
|
/// Size of values returned by <see cref="ReadValue(void*,int)"/>.
|
|
/// </summary>
|
|
/// <value>Size of value returned when reading.</value>
|
|
/// <remarks>
|
|
/// All input values passed around by the system are required to be "blittable",
|
|
/// i.e. they cannot contain references, cannot be heap objects themselves, and
|
|
/// must be trivially mem-copyable. This means that any value can be read out
|
|
/// and retained in a raw byte buffer.
|
|
///
|
|
/// The value of this property determines how many bytes will be written
|
|
/// by <see cref="ReadValue(void*,int)"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="InputControl.valueSizeInBytes"/>
|
|
/// <seealso cref="InputBindingComposite.valueSizeInBytes"/>
|
|
/// <seealso cref="ReadValue(void*,int)"/>
|
|
public int valueSizeInBytes
|
|
{
|
|
get
|
|
{
|
|
if (m_State == null)
|
|
return 0;
|
|
|
|
return m_State.GetValueSizeInBytes(bindingIndex, controlIndex);
|
|
}
|
|
}
|
|
|
|
////TODO: need ability to read as button
|
|
|
|
/// <summary>
|
|
/// Read the value of the action as a raw byte buffer. This allows reading
|
|
/// values without having to know value types but also, unlike <see cref="ReadValueAsObject"/>,
|
|
/// without allocating GC heap memory.
|
|
/// </summary>
|
|
/// <param name="buffer">Memory buffer to read the value into.</param>
|
|
/// <param name="bufferSize">Size of buffer allocated at <paramref name="buffer"/>. Must be
|
|
/// at least <see cref="valueSizeInBytes"/>.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception>
|
|
/// <exception cref="ArgumentException"><paramref name="bufferSize"/> is too small.</exception>
|
|
/// <remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// // Read a Vector2 using the raw memory ReadValue API.
|
|
/// // Here we just read into a local variable which we could
|
|
/// // just as well (and more easily) do using ReadValue<Vector2>.
|
|
/// // Still, it serves as a demonstration for how the API
|
|
/// // operates in general.
|
|
/// unsafe
|
|
/// {
|
|
/// var value = default(Vector2);
|
|
/// var valuePtr = UnsafeUtility.AddressOf(ref value);
|
|
/// context.ReadValue(buffer, UnsafeUtility.SizeOf<Vector2>());
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
/// </remarks>
|
|
/// <seealso cref="InputControlExtensions.ReadValueIntoBuffer"/>
|
|
/// <seealso cref="InputAction.ReadValue{TValue}"/>
|
|
/// <seealso cref="ReadValue{TValue}"/>
|
|
public unsafe void ReadValue(void* buffer, int bufferSize)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
|
|
if (m_State != null && phase.IsInProgress())
|
|
{
|
|
m_State.ReadValue(bindingIndex, controlIndex, buffer, bufferSize);
|
|
}
|
|
else
|
|
{
|
|
var valueSize = valueSizeInBytes;
|
|
if (bufferSize < valueSize)
|
|
throw new ArgumentException(
|
|
$"Expected buffer of at least {valueSize} bytes but got buffer of only {bufferSize} bytes", nameof(bufferSize));
|
|
UnsafeUtility.MemClear(buffer, valueSizeInBytes);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the value of the action.
|
|
/// </summary>
|
|
/// <typeparam name="TValue">Type of value to read. This must correspond to the
|
|
/// expected by either <see cref="control"/> or, if it is a composite, by the
|
|
/// <see cref="InputBindingComposite"/> in use.</typeparam>
|
|
/// <returns>The value read from the action.</returns>
|
|
/// <exception cref="InvalidOperationException">The given type <typeparamref name="TValue"/>
|
|
/// does not match the value type expected by the control or binding composite.</exception>
|
|
/// <seealso cref="InputAction.ReadValue{TValue}"/>
|
|
/// <seealso cref="ReadValue(void*,int)"/>
|
|
/// <seealso cref="ReadValueAsObject"/>
|
|
public TValue ReadValue<TValue>()
|
|
where TValue : struct
|
|
{
|
|
var value = default(TValue);
|
|
if (m_State != null)
|
|
{
|
|
value = phase.IsInProgress() ?
|
|
m_State.ReadValue<TValue>(bindingIndex, controlIndex) :
|
|
m_State.ApplyProcessors(bindingIndex, value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the current value of the action as a <c>float</c> and return true if it is equal to
|
|
/// or greater than the button press threshold.
|
|
/// </summary>
|
|
/// <returns>True if the action is considered in "pressed" state, false otherwise.</returns>
|
|
/// <remarks>
|
|
/// If the currently active control is a <see cref="ButtonControl"/>, the <see cref="ButtonControl.pressPoint"/>
|
|
/// of the button will be taken into account (if set). If there is no custom button press point, the
|
|
/// global <see cref="InputSettings.defaultButtonPressPoint"/> will be used.
|
|
/// </remarks>
|
|
/// <seealso cref="InputSettings.defaultButtonPressPoint"/>
|
|
/// <seealso cref="ButtonControl.pressPoint"/>
|
|
public bool ReadValueAsButton()
|
|
{
|
|
var value = false;
|
|
if (m_State != null && phase.IsInProgress())
|
|
value = m_State.ReadValueAsButton(bindingIndex, controlIndex);
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Same as <see cref="ReadValue{TValue}"/> except that it is not necessary to
|
|
/// know the type of value at compile time.
|
|
/// </summary>
|
|
/// <returns>The current value from the binding that triggered the action or <c>null</c> if the action
|
|
/// is not currently in progress.</returns>
|
|
/// <remarks>
|
|
/// This method allocates GC heap memory. Using it during normal gameplay will lead
|
|
/// to frame-rate instabilities.
|
|
/// </remarks>
|
|
/// <seealso cref="ReadValue{TValue}"/>
|
|
/// <seealso cref="InputAction.ReadValueAsObject"/>
|
|
public object ReadValueAsObject()
|
|
{
|
|
if (m_State != null && phase.IsInProgress())
|
|
return m_State.ReadValueAsObject(bindingIndex, controlIndex);
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a string representation of the context useful for debugging.
|
|
/// </summary>
|
|
/// <returns>String representation of the context.</returns>
|
|
public override string ToString()
|
|
{
|
|
return $"{{ action={action} phase={phase} time={time} control={control} value={ReadValueAsObject()} interaction={interaction} }}";
|
|
}
|
|
}
|
|
}
|
|
}
|