| 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"/> | |
| [] | |
| 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() | |
| { | |
| } | |
| /// <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 | |
| } | |
| }; | |
| m_BindingsStartIndex = 0; | |
| m_BindingsCount = 1; | |
| } | |
| else | |
| { | |
| m_Interactions = interactions; | |
| m_Processors = processors; | |
| } | |
| m_ExpectedControlType = expectedControlType; | |
| } | |
| /// <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, | |
| }; | |
| 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 | |
| [ | |
| ] | |
| [] internal string m_Name; | |
| [ | |
| ] | |
| [] internal InputActionType m_Type; | |
| [] | |
| [ | |
| ] | |
| [] internal string m_ExpectedControlType; | |
| [ | |
| ] | |
| [] internal string m_Id; // Can't serialize System.Guid and Unity's GUID is editor only. | |
| [] internal string m_Processors; | |
| [] 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. | |
| [] internal InputBinding[] m_SingletonActionBindings; | |
| [] internal ActionFlags m_Flags; | |
| [] internal InputBinding? m_BindingMask; | |
| [] internal int m_BindingsStartIndex; | |
| [] internal int m_BindingsCount; | |
| [] internal int m_ControlStartIndex; | |
| [] 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"/> | |
| [] 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> | |
| [] internal InputActionMap m_ActionMap; | |
| // Listeners. No array allocations if only a single listener. | |
| [] internal CallbackArray<Action<CallbackContext>> m_OnStarted; | |
| [] internal CallbackArray<Action<CallbackContext>> m_OnCanceled; | |
| [] 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); | |
| [] | |
| 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} }}"; | |
| } | |
| } | |
| } | |
| } | |