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
{
///
/// A named input signal that can flexibly decide which input data to tap.
///
///
/// 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.
///
///
///
/// // 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");
///
///
///
/// Bindings use control path expressions to reference controls. See
/// for more details. There may be arbitrary many bindings targeting a single action. The
/// list of bindings targeting an action can be obtained through .
///
/// By itself an action does not do anything until it is enabled:
///
///
///
/// action.Enable();
///
///
///
/// Once enabled, the action will actively monitor all controls on devices present
/// in the system (see ) 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
/// or, if the action is part of an ,
/// by setting the property of the action map. The
/// controls that an action uses can be queried using the property.
///
/// When input is received on controls bound to an action, the action will trigger callbacks
/// in response. These callbacks are , , and
/// . The callbacks are triggered as part of input system updates
/// (see ), i.e. they happen before the respective
/// MonoBehaviour.Update or MonoBehaviour.FixedUpdate methods
/// get executed (depending on which the system is
/// set to).
///
/// In what order and how those callbacks get triggered depends on both the
/// of the action as well as on the interactions (see ) 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), is called and then
/// . Subsequently, whenever the a control further changes value to
/// anything other than its default value, will be called again.
/// Finally, when the control moves back to its default value (i.e. resting position),
/// is called.
///
/// To hook into the callbacks, there are several options available to you. The most obvious
/// one is to hook directly into , , and/or
/// . In these callbacks, you will receive a
/// with information about how the action got triggered. For example, you can use to read the value from the binding that triggered
/// or use to find the interaction that is in progress.
///
///
///
/// action.started += context => Debug.Log($"{context.action} started");
/// action.performed += context => Debug.Log($"{context.action} performed");
/// action.canceled += context => Debug.Log($"{context.action} canceled");
///
///
///
/// Alternatively, you can use the callback for
/// actions that are part of an action map or the global
/// callback to globally listen for action activity. To simply record action activity instead
/// of responding to it directly, you can use .
///
/// If you prefer to poll an action directly as part of your MonoBehaviour.Update
/// or MonoBehaviour.FixedUpdate logic, you can do so using the
/// and methods.
///
///
///
/// 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>();
/// }
///
///
///
/// 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 ) which can in turn
/// be grouped into assets (see ).
///
/// 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 manual.
///
///
///
///
[Serializable]
public sealed class InputAction : ICloneable, IDisposable
{
///
/// Name of the action.
///
/// Plain-text name of the action.
///
/// Can be null for anonymous actions created in code.
///
/// If the action is part of an , 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 ..
///
///
public string name => m_Name;
///
/// Behavior type of the action.
///
/// General behavior type of the action.
///
/// Determines how the action gets triggered in response to control value changes.
///
/// For details about how the action type affects an action, see .
///
public InputActionType type => m_Type;
///
/// A stable, unique identifier for the action.
///
/// Unique ID of the action.
///
/// 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.
///
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);
}
}
///
/// Name of control layout expected for controls bound to this action.
///
///
/// 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.
///
public string expectedControlType
{
get => m_ExpectedControlType;
set => m_ExpectedControlType = value;
}
///
/// Processors applied to every binding on the action.
///
/// Processors added to all bindings on the action.
///
/// This property is equivalent to appending the same string to the
/// 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.
///
///
///
/// 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");
///
///
///
///
///
///
public string processors => m_Processors;
///
/// Interactions applied to every binding on the action.
///
/// Interactions added to all bindings on the action.
///
/// This property is equivalent to appending the same string to the
/// 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.
///
///
///
/// 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");
///
///
///
///
///
///
public string interactions => m_Interactions;
///
/// The map the action belongs to.
///
/// that the action belongs to or null.
///
/// If the action is a loose action created in code, this will be null.
///
///
///
/// var action1 = new InputAction(); // action1.actionMap will be null
///
/// var actionMap = new InputActionMap();
/// var action2 = actionMap.AddAction("action"); // action2.actionMap will point to actionMap
///
///
///
///
public InputActionMap actionMap => isSingletonAction ? null : m_ActionMap;
///
/// An optional mask that determines which bindings of the action to enable and
/// which to ignore.
///
/// Optional mask that determines which bindings on the action to enable.
///
/// Binding masks can be applied at three different levels: for an entire asset through
/// , for a specific map through , and for single actions through this property.
/// By default, none of the masks will be set (i.e. they will be null).
///
/// 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 .
///
/// Note that if you modify the masks applicable to an action while it is
/// enabled, the action's 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
/// property.
///
///
///
/// // 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"
/// };
///
///
///
///
///
///
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);
}
}
///
/// The list of bindings associated with the action.
///
/// List of bindings for the action.
///
/// This list contains all bindings from of the action's
/// that reference the action through their
/// 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.
///
///
public ReadOnlyArray bindings => GetOrCreateActionMap().GetBindingsForSingleAction(this);
///
/// The set of controls to which the action's resolve.
///
/// Controls resolved from the action's .
///
/// 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 () and device restrictions ().
///
/// 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 Binding Resolution
/// in the manual for details.
///
/// To map a control in this array to an index into , use
/// .
///
///
///
/// // Map control list to binding indices.
/// var bindingIndices = myAction.controls.Select(c => myAction.GetBindingIndexForControl(c));
///
///
///
/// 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.
///
///
///
/// 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;
///
///
///
///
///
public ReadOnlyArray controls
{
get
{
var map = GetOrCreateActionMap();
map.ResolveBindingsIfNecessary();
return map.GetControlsForSingleAction(this);
}
}
///
/// The current phase of the action.
///
///
/// 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
/// and when it stops receiving input, it will go to .
/// When is used depends primarily on the type
/// of action. will trigger
/// whenever the value of the control changes (including the first time; i.e. it will first
/// trigger and then
/// right after) whereas will trigger
/// as soon as the button press threshold ()
/// has been crossed.
///
/// Note that both interactions and the action can affect the phases
/// that an action goes through. actions will
/// only ever use and not go to or (as
/// pass-through actions do not follow the start-performed-canceled model in general).
///
/// While an action is disabled, its phase is .
///
public InputActionPhase phase => currentState.phase;
///
/// True if the action is currently in or
/// phase. False in all other cases.
///
///
public bool inProgress => phase.IsInProgress();
///
/// Whether the action is currently enabled, i.e. responds to input, or not.
///
/// True if the action is currently enabled.
///
/// An action is enabled by either calling on it directly or by calling
/// on the containing the action.
/// When enabled, an action will listen for changes on the controls it is bound to and trigger
/// callbacks such as , , and
/// in response.
///
///
///
///
///
///
public bool enabled => phase != InputActionPhase.Disabled;
///
/// Event that is triggered when the action has been started.
///
///
/// See for details of how an action progresses through phases
/// and triggers this callback.
///
///
public event Action started
{
add => m_OnStarted.AddCallback(value);
remove => m_OnStarted.RemoveCallback(value);
}
///
/// Event that is triggered when the action has been
/// but then canceled before being fully .
///
///
/// See for details of how an action progresses through phases
/// and triggers this callback.
///
///
public event Action canceled
{
add => m_OnCanceled.AddCallback(value);
remove => m_OnCanceled.RemoveCallback(value);
}
///
/// Event that is triggered when the action has been fully performed.
///
///
/// See for details of how an action progresses through phases
/// and triggers this callback.
///
///
public event Action performed
{
add => m_OnPerformed.AddCallback(value);
remove => m_OnPerformed.RemoveCallback(value);
}
////TODO: Obsolete and drop this when we can break API
///
/// Equivalent to .
///
///
public bool triggered => WasPerformedThisFrame();
///
/// The currently active control that is driving the action. while the action
/// is in waiting () or canceled ()
/// state. Otherwise the control that last had activity on it which wasn't ignored.
///
///
/// Note that the control's value does not necessarily correspond to the value of the
/// action () as the control may be part of a composite.
///
///
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;
}
}
///
/// Type of value returned by and currently expected
/// by . while the action
/// is in waiting () or canceled ()
/// state as this is based on the currently active control that is driving the action.
///
/// Type of object returned when reading a value.
///
/// The type of value returned by an action is usually determined by the
/// that triggered the action, i.e. by the
/// control referenced from .
///
/// 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).
///
/// The active value type may change depending on which controls are actuated if there are multiple
/// bindings with different control types. This property can be used to ensure you are calling the
/// method with the expected type parameter if your action is
/// configured to allow multiple control types as otherwise that method will throw an
/// if the type of the control that triggered the action does not match the type parameter.
///
///
///
///
public unsafe Type activeValueType
{
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.GetValueType(actionStatePtr->bindingIndex, controlIndex);
}
return null;
}
}
///
/// Whether the action wants a state check on its bound controls as soon as it is enabled. This is always
/// true for actions but can optionally be enabled for
/// or actions.
///
///
/// Usually, when an action is (e.g. via ), it will start listening for input
/// and then trigger once the first input arrives. However, bound to an action may already be
/// actuated when an action is enabled. For example, if a "jump" action is bound to ,
/// the space bar may already be pressed when the jump action is enabled.
///
/// actions handle this differently by immediately performing an "initial state check"
/// in the next input update (see ) 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 and
/// actions using this property.
///
///
///
public bool wantsInitialStateCheck
{
get => type == InputActionType.Value || (m_Flags & ActionFlags.WantsInitialStateCheck) != 0;
set
{
if (value)
m_Flags |= ActionFlags.WantsInitialStateCheck;
else
m_Flags &= ~ActionFlags.WantsInitialStateCheck;
}
}
///
/// Construct an unnamed, free-standing action that is not part of any map or asset
/// and has no bindings. Bindings can be added with .
/// The action type defaults to .
///
///
/// The action will not have an associated and
/// will thus be null. Use 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 to enable the action.
///
///
///
/// // 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();
///
///
///
public InputAction()
{
m_Id = Guid.NewGuid().ToString();
}
///
/// Construct a free-standing action that is not part of an .
///
/// Name of the action. If null or empty, the action will be unnamed.
/// Type of action to create. Defaults to , i.e.
/// an action that provides continuous values.
/// 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 .
/// If is not null or empty, this parameter represents
/// the interaction to apply to the newly created binding (i.e. ). If
/// is not supplied, this parameter represents the interactions to apply to the action
/// (i.e. the value of ).
/// If is not null or empty, this parameter represents
/// the processors to apply to the newly created binding (i.e. ). If
/// is not supplied, this parameter represents the processors to apply to the
/// action (i.e. the value of ).
/// The optional expected control type for the action (i.e. ).
///
/// The action will not have an associated and
/// will thus be null. Use 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 to enable the action.
///
/// Additional bindings can be added with .
///
///
///
/// // 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();
///
///
///
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();
}
///
/// Release internal state held on to by the action.
///
///
/// 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 s, the state is shared by all
/// actions in the map and, if the map itself is part of an ,
/// 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.
///
public void Dispose()
{
m_ActionMap?.m_State?.Dispose();
}
///
/// Return a string version of the action. Mainly useful for debugging.
///
/// A string version of the action.
public override string ToString()
{
string str;
if (m_Name == null)
str = "";
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;
}
///
/// Enable the action such that it actively listens for input and runs callbacks
/// in response.
///
///
/// If the action is already enabled, this method does nothing.
///
/// By default, actions start out disabled, i.e. with being false.
/// When enabled, two things happen.
///
/// First, if it hasn't already happened, an action will resolve all of its bindings
/// to 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 bound to an action, change monitors (see
/// ) will be added to the system. If any of the
/// controls changes state in the future, the action will get notified and respond.
///
/// 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-default 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 . To enable all actions in a map,
/// call .
///
/// The associated with an action (if any), will immediately
/// toggle to being enabled (see ) 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 s, the state
/// is shared by all actions in the map and, if the map itself is part of an , also by all the maps that are part of the asset.
///
/// To dispose of the state, call .
///
///
///
/// 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();
///
///
///
///
///
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);
}
///
/// Disable the action such that is stop listening/responding to input.
///
///
/// If the action is already disabled, this method does nothing.
///
/// If the action is currently in progress, i.e. if is
/// , the action will be canceled as
/// part of being disabled. This means that you will see a call on
/// from within the call to Disable().
///
///
///
public void Disable()
{
if (!enabled)
return;
m_ActionMap.m_State.DisableSingleAction(this);
}
////REVIEW: is *not* cloning IDs here really the right thing to do?
///
/// Return an identical instance of the action.
///
/// An identical clone of the action
///
/// Note that if you clone an action that is part of an ,
/// 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 of the action is not cloned. Instead, the
/// clone will receive a new unique ID. Also, callbacks install on events such
/// as will not be copied over to the clone.
///
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;
}
///
/// Return an boxed instance of the action.
///
/// An boxed clone of the action
///
object ICloneable.Clone()
{
return Clone();
}
////TODO: ReadValue(void*, int)
///
/// 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.
///
/// Value type to read. Must match the value type of the binding/control that triggered.
/// The current value of the control/binding that is driving this action with all binding processors applied.
///
/// This method can be used as an alternative to hooking into , ,
/// and/or and reading out the value using
/// there. Instead, this API acts more like a polling API that can be called, for example, as part of
/// MonoBehaviour.Update.
///
///
///
/// // 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);
/// //...
/// }
/// }
///
///
///
/// If the action has button-like behavior, then is usually a better alternative to
/// reading out a float and checking if it is above the button press point.
///
/// The given type does not match
/// the value type of the control or composite currently driving the action.
///
///
///
public unsafe TValue ReadValue()
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(actionStatePtr->bindingIndex, actionStatePtr->controlIndex)
: state.ApplyProcessors(actionStatePtr->bindingIndex, default(TValue));
}
///
/// Same as but read the value without having to know the value type
/// of the action.
///
/// The current value of the action or null if the action is not currently in
/// or phase.
///
/// This method allocates GC memory and is thus not a good choice for getting called as part of gameplay
/// logic.
///
///
///
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;
}
///
/// Read the current amount of actuation of the control that is driving this action.
///
/// Returns the current level of control actuation (usually [0..1]) or -1 if
/// the control is actuated but does not support computing magnitudes.
///
/// Magnitudes do not make sense for all types of controls. Controls that have no meaningful magnitude
/// will return -1 when calling this method. Any negative magnitude value should be considered an invalid value.
///
/// The magnitude returned by an action is usually determined by the
/// that triggered the action, i.e. by the
/// control referenced from .
///
/// However, if the binding that triggered is a composite, then the composite
/// will determine the magnitude and not the individual control that triggered.
/// Instead, the value of the control that triggered the action will be fed into the composite magnitude calculation.
///
///
///
public unsafe float GetControlMagnitude()
{
var state = GetOrCreateActionMap().m_State;
if (state != null)
{
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
if (actionStatePtr->haveMagnitude)
return actionStatePtr->magnitude;
}
return 0f;
}
///
/// Reset the action state to default.
///
///
/// 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 .
///
/// Note that invoking this method will not modify enabled state.
///
///
///
///
///
public void Reset()
{
var state = GetOrCreateActionMap().m_State;
state?.ResetActionState(m_ActionIndexInState, toPhase: enabled ? InputActionPhase.Waiting : InputActionPhase.Disabled, hardReset: true);
}
///
/// Check whether the current actuation of the action has crossed the button press threshold (see
/// ) and has not yet fallen back below the
/// release threshold (see ).
///
/// True if the action is considered to be in "pressed" state, false otherwise.
///
/// This method is different from simply reading the action's current float value and comparing
/// it to the press threshold and is also different from comparing the current actuation of
/// 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 of action, not just buttons.
///
/// Also note that because this operates on the results of ,
/// it works with many kind of controls, not just buttons. For example, if an action is bound
/// to a , 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 )
/// are respected and will take precedence over .
///
///
///
/// var up = playerInput.actions["up"];
/// if (up.IsPressed())
/// transform.Translate(0, 10 * Time.deltaTime, 0);
///
///
///
/// 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.
///
///
///
///
///
///
public unsafe bool IsPressed()
{
var state = GetOrCreateActionMap().m_State;
if (state != null)
{
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
return actionStatePtr->isPressed;
}
return false;
}
///
/// Whether the action has been or .
///
/// True if the action is currently triggering.
///
public unsafe bool IsInProgress()
{
var state = GetOrCreateActionMap().m_State;
if (state != null)
{
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
return actionStatePtr->phase.IsInProgress();
}
return false;
}
///
/// Returns true if the action's value crossed the press threshold (see )
/// at any point in the frame.
///
/// True if the action was pressed this frame.
///
/// This method is different from in that it is not bound
/// to . Instead, if the action's level of actuation (that is, the level of
/// magnitude -- see -- of the control(s) bound
/// to the action) crossed the press threshold (see )
/// 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 of action, not just buttons.
///
/// Also note that because this operates on the results of ,
/// it works with many kind of controls, not just buttons. For example, if an action is bound
/// to a , 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 )
/// are respected and will take precedence over .
///
///
///
/// var fire = playerInput.actions["fire"];
/// if (fire.WasPressedThisFrame() && fire.IsPressed())
/// StartFiring();
/// else if (fire.WasReleasedThisFrame())
/// StopFiring();
///
///
///
/// 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 (MonoBehaviour.Update) or the current
/// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting.
///
///
///
///
///
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;
}
///
/// Returns true if the action's value crossed the release threshold (see )
/// at any point in the frame after being in pressed state.
///
/// True if the action was released this frame.
///
/// This method works with any of action, not just buttons.
///
/// Also note that because this operates on the results of ,
/// it works with many kind of controls, not just buttons. For example, if an action is bound
/// to a , 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 )
/// are respected and will take precedence over .
///
///
///
/// var fire = playerInput.actions["fire"];
/// if (fire.WasPressedThisFrame() && fire.IsPressed())
/// StartFiring();
/// else if (fire.WasReleasedThisFrame())
/// StopFiring();
///
///
///
/// 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 (MonoBehaviour.Update) or the current
/// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting.
///
///
///
///
///
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()?)
///
/// Check whether was at any point
/// in the current frame.
///
/// True if the action performed this frame.
///
/// This method is different from 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
/// assigned to it. In the frame where the space bar
/// is pressed, will be true (because the button/key is now pressed)
/// but WasPerformedThisFrame will still be false (because the hold has not been performed yet).
/// Only after the hold time has expired will WasPerformedThisFrame be true and only in the frame
/// where the hold performed.
///
/// This is different from checking 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,
/// might no longer be , whereas WasPerformedThisFrame will remain
/// true for the entirety of the frame regardless of what else the action does.
///
/// Unlike , 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
/// runs) as long as the action was triggered at least once.
///
///
///
/// var warp = playerInput.actions["Warp"];
/// if (warp.WasPerformedThisFrame())
/// InitiateWarp();
///
///
///
/// 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 (MonoBehaviour.Update) or the current
/// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting.
///
///
///
///
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;
}
///
/// Check whether transitioned from to any other phase
/// value at least once in the current frame.
///
/// True if the action completed this frame.
///
/// Although is technically a phase, this method does not consider disabling
/// the action while the action is in to be "completed".
///
/// This method is different from 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
/// assigned to it. In the frame where the space bar
/// is pressed, will be true (because the button/key is now pressed)
/// but will still be false (because the hold has not been performed yet).
/// If at that time the space bar is released, will be true (because the
/// button/key is now released) but WasCompletedThisFrame will still be false (because the hold
/// had not been performed yet). If instead the space bar is held down for long enough for the hold interaction,
/// the phase will change to and stay and
/// will be true for one frame as it meets the duration threshold. Once released, WasCompletedThisFrame will be true
/// (because the action is no longer performed) and only in the frame where the hold transitioned away from Performed.
///
/// For another example where the action could be considered pressed but also completed, let's say the action
/// is bound to the thumbstick and that the binding has a Sector interaction from the XR Interaction Toolkit assigned
/// to it such that it only performs in the forward sector area past a button press threshold. In the frame where the
/// thumbstick is pushed forward, both will be true (because the thumbstick actuation is
/// now considered pressed) and will be true (because the thumbstick is in
/// the forward sector). If the thumbstick is then moved to the left in a sweeping motion,
/// will still be true. However, WasCompletedThisFrame will also be true (because the thumbstick is
/// no longer in the forward sector while still crossed the button press threshold) and only in the frame where
/// the thumbstick was no longer within the forward sector. For more details about the Sector interaction, see
/// SectorInteraction
/// in the XR Interaction Toolkit Scripting API documentation.
///
/// Unlike , 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
/// runs) as long as the action was completed at least once.
///
///
///
/// var teleport = playerInput.actions["Teleport"];
/// if (teleport.WasPerformedThisFrame())
/// InitiateTeleport();
/// else if (teleport.WasCompletedThisFrame())
/// StopTeleport();
///
///
///
/// 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 (MonoBehaviour.Update) or the current
/// fixed update (MonoBehaviour.FixedUpdate) depending on the value of the setting.
///
///
///
///
public unsafe bool WasCompletedThisFrame()
{
var state = GetOrCreateActionMap().m_State;
if (state != null)
{
var actionStatePtr = &state.actionStates[m_ActionIndexInState];
var currentUpdateStep = InputUpdate.s_UpdateStepCount;
return actionStatePtr->lastCompletedInUpdate == currentUpdateStep && currentUpdateStep != default;
}
return false;
}
///
/// Return the completion percentage of the timeout (if any) running on the current interaction.
///
/// A value >= 0 (no progress) and <= 1 (finished) indicating the level of completion
/// of the currently running timeout.
///
/// This method is useful, for example, when providing UI feedback for an ongoing action. If, say,
/// you have a 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 ),
/// 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
/// 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 .
///
/// The meaning of the timeout is dependent on the interaction in play. For a ,
/// "completion" represents the duration timeout (that is, the time until a "hold" is considered to be performed), whereas
/// for a "completion" 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 .
/// In this case, progression towards a single timeout does not necessarily mean progression towards completion
/// of the whole interaction. An interaction can call
/// to inform the Input System of the total length of timeouts to run. If this is done, the result of the
/// GetTimeoutCompletionPercentage method will return a value reflecting the progression with respect
/// to total time.
///
///
///
/// // 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);
/// }
///
///
///
///
///
///
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;
///
/// Index of the action in the associated with the
/// action's .
///
///
/// This is not necessarily the same as the index of the action in its map.
///
///
[NonSerialized] internal int m_ActionIndexInState = InputActionState.kInvalidIndex;
///
/// The action map that owns the action.
///
///
/// This is not serialized. The action map will restore this back references after deserialization.
///
[NonSerialized] internal InputActionMap m_ActionMap;
// Listeners. No array allocations if only a single listener.
[NonSerialized] internal CallbackArray> m_OnStarted;
[NonSerialized] internal CallbackArray> m_OnCanceled;
[NonSerialized] internal CallbackArray> m_OnPerformed;
///
/// Whether the action is a loose action created in code (e.g. as a property on a component).
///
///
/// 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.
///
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
// "/#(รค)" 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?)
///
/// Information provided to action callbacks about what triggered an action.
///
///
/// This struct should not be held on to past the duration of the callback.
///
///
///
///
///
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;
///
/// Current phase of the action. Equivalent to accessing
/// on .
///
/// Current phase of the action.
///
///
///
///
public unsafe InputActionPhase phase
{
get
{
if (m_State == null)
return InputActionPhase.Disabled;
return m_State.actionStates[actionIndex].phase;
}
}
///
/// Whether the has just been started.
///
/// If true, the action was just started.
///
public bool started => phase == InputActionPhase.Started;
///
/// Whether the has just been performed.
///
/// If true, the action was just performed.
///
public bool performed => phase == InputActionPhase.Performed;
///
/// Whether the has just been canceled.
///
/// If true, the action was just canceled.
///
public bool canceled => phase == InputActionPhase.Canceled;
///
/// The action that got triggered.
///
/// Action that got triggered.
public InputAction action => m_State?.GetActionOrNull(bindingIndex);
///
/// The control that triggered the action.
///
/// Control that triggered the action.
///
/// 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 in response to a timeout.
/// For example, a will cancel itself if the
/// button control is not released within a certain time. When this happens, the control
/// property will be the control that last fed input into the action.
///
///
///
public InputControl control => m_State?.controls[controlIndex];
///
/// The interaction that triggered the action or null if the binding that triggered does not
/// have any particular interaction set on it.
///
/// Interaction that triggered the callback.
///
///
///
/// 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();
/// }
///
///
///
///
///
public IInputInteraction interaction
{
get
{
if (m_State == null)
return null;
var index = interactionIndex;
if (index == InputActionState.kInvalidIndex)
return null;
return m_State.interactions[index];
}
}
///
/// The time at which the action got triggered.
///
/// Time relative to Time.realtimeSinceStartup at which
/// the action got triggered.
///
/// 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 not the
/// value of Time.realtimeSinceStartup when the input system calls the
/// callback but rather the time at which the input was generated that triggered
/// the action.
///
///
public unsafe double time
{
get
{
if (m_State == null)
return 0;
return m_State.actionStates[actionIndex].time;
}
}
///
/// Time at which the action was started.
///
/// Value relative to Time.realtimeSinceStartup when the action
/// changed to .
///
/// This is only relevant for actions that go through distinct a
/// cycle as driven by interactions.
///
/// The value of this property is that of when was called. See the
/// property for how the timestamp works.
///
public unsafe double startTime
{
get
{
if (m_State == null)
return 0;
return m_State.actionStates[actionIndex].startTime;
}
}
///
/// Time difference between and .
///
/// Difference between and .
///
/// This property can be used, for example, to determine how long a button
/// was held down.
///
///
///
/// // 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...
/// }
/// };
///
///
///
public double duration => time - startTime;
///
/// Type of value returned by and expected
/// by .
///
/// Type of object returned when reading a value.
///
/// The type of value returned by an action is usually determined by the
/// that triggered the action, i.e. by the
/// control referenced from .
///
/// 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).
///
///
///
///
public Type valueType => m_State?.GetValueType(bindingIndex, controlIndex);
///
/// Size of values returned by .
///
/// Size of value returned when reading.
///
/// 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 .
///
///
///
///
public int valueSizeInBytes
{
get
{
if (m_State == null)
return 0;
return m_State.GetValueSizeInBytes(bindingIndex, controlIndex);
}
}
///
/// Read the value of the action as a raw byte buffer. This allows reading
/// values without having to know value types but also, unlike ,
/// without allocating GC heap memory.
///
/// Memory buffer to read the value into.
/// Size of buffer allocated at . Must be
/// at least .
/// is null.
/// is too small.
///
///
///
/// // 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>());
/// }
///
///
///
///
///
///
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);
}
}
///
/// Read the value of the action.
///
/// Type of value to read. This must correspond to the
/// expected by either or, if it is a composite, by the
/// in use.
/// The value read from the action.
/// The given type
/// does not match the value type expected by the control or binding composite.
///
///
///
public TValue ReadValue()
where TValue : struct
{
var value = default(TValue);
if (m_State != null)
{
value = phase.IsInProgress() ?
m_State.ReadValue(bindingIndex, controlIndex) :
m_State.ApplyProcessors(bindingIndex, value);
}
return value;
}
///
/// Read the current value of the action as a float and return true if it is equal to
/// or greater than the button press threshold.
///
/// True if the action is considered in "pressed" state, false otherwise.
///
/// If the currently active control is a , the
/// of the button will be taken into account (if set). If there is no custom button press point, the
/// global will be used.
///
///
///
public bool ReadValueAsButton()
{
var value = false;
if (m_State != null && phase.IsInProgress())
value = m_State.ReadValueAsButton(bindingIndex, controlIndex);
return value;
}
///
/// Same as except that it is not necessary to
/// know the type of value at compile time.
///
/// The current value from the binding that triggered the action or null if the action
/// is not currently in progress.
///
/// This method allocates GC heap memory. Using it during normal gameplay will lead
/// to frame-rate instabilities.
///
///
///
public object ReadValueAsObject()
{
if (m_State != null && phase.IsInProgress())
return m_State.ReadValueAsObject(bindingIndex, controlIndex);
return null;
}
///
/// Return a string representation of the context useful for debugging.
///
/// String representation of the context.
public override string ToString()
{
return $"{{ action={action} phase={phase} time={time} control={control} value={ReadValueAsObject()} interaction={interaction} }}";
}
}
}
}