18 KiB
Responding to Actions
There are two main techniques you can use to respond to Actions in your project. These are to either use polling or an event-driven approach.
- The Polling approach refers to the technique of repeatedly checking the current state of the Actions you are interested in. Typically you would do this in the
Update()
method of aMonoBehaviour
script. - The Event-driven approach involves creating your own methods in code that are automatically called when an action is performed.
For most common scenarios, especially action games where the user's input should have a continuous effect on an in-game character, Polling is usually simpler and easier to implement.
For other situations where input is less frequent and directed to various different GameObjects in your scene, an event-driven approach might be more suitable.
Polling Actions
You can poll the current value of an Action using InputAction.ReadValue<>()
:
using UnityEngine;
using UnityEngine.InputSystem;
public class Example : MonoBehaviour
{
InputAction moveAction;
private void Start()
{
moveAction = InputSystem.actions.FindAction("Move");
}
void Update()
{
Vector2 moveValue = moveAction.ReadValue<Vector2>();
// your code would then use moveValue to apply movement
// to your GameObject
}
}
Note that the value type has to correspond to the value type of the control that the value is being read from.
There are two methods you can use to poll for performed
action callbacks to determine whether an action was performed or stopped performing in the current frame.
These methods differ from InputAction.WasPressedThisFrame()
and InputAction.WasReleasedThisFrame()
in that these depend directly on the Interactions driving the action (including the default Interaction if no specific interaction has been added to the action or binding).
Method | Description |
---|---|
InputAction.WasPerformedThisFrame() |
True if the InputAction.phase of the action has, at any point during the current frame, changed to Performed . |
InputAction.WasCompletedThisFrame() |
True if the InputAction.phase of the action has, at any point during the current frame, changed away from Performed to any other phase. This can be useful for Button actions or Value actions with interactions like Press or Hold when you want to know the frame the interaction stops being performed. For actions with the default Interaction, this method will always return false for Value and Pass-Through actions (since the phase stays in Started for Value actions and stays in Performed for Pass-Through). |
This example uses the Interact action from the default actions, which has a Hold interaction to make it perform only after the bound control is held for a period of time (for example, 0.4 seconds):
using UnityEngine;
using UnityEngine.InputSystem;
public class Example : MonoBehaviour
{
InputAction interactAction;
private void Start()
{
interactAction = InputSystem.actions.FindAction("Interact");
}
void Update()
{
if (interactAction.WasPerformedThisFrame())
{
// your code to respond to the first frame that the Interact action is held for enough time
}
if (interactAction.WasCompletedThisFrame())
{
// your code to respond to the frame that the Interact action is released after being held for enough time
}
}
}
Finally, there are three methods you can use to poll for button presses and releases:
Method | Description |
---|---|
InputAction.IsPressed() |
True if the level of actuation on the action has crossed the press point and did not yet fall to or below the release threshold. |
InputAction.WasPressedThisFrame() |
True if the level of actuation on the action has, at any point during the current frame, reached or gone above the press point. |
InputAction.WasReleasedThisFrame() |
True if the level of actuation on the action has, at any point during the current frame, gone from being at or above the press point to at or below the release threshold. |
This example uses three actions called Shield, Teleport and Submit (which are not included in the default actions):
using UnityEngine;
using UnityEngine.InputSystem;
public class Example : MonoBehaviour
{
InputAction shieldAction;
InputAction teleportAction;
InputAction submitAction;
private void Start()
{
shieldAction = InputSystem.actions.FindAction("Shield");
teleportAction = InputSystem.actions.FindAction("Teleport");
submitAction = InputSystem.actions.FindAction("Submit");
}
void Update()
{
if (shieldAction.IsPressed())
{
// shield is active for every frame that the shield action is pressed
}
if (teleportAction.WasPressedThisFrame())
{
// teleport occurs on the first frame that the action is pressed, and not again until the button is released
}
if (submit.WasReleasedThisFrame())
{
// submit occurs on the frame that the action is released, a common technique for buttons relating to UI controls.
}
}
}
Responding to Actions using callbacks
When you set up callbacks for your Action, the Action informs your code that a certain type of input has occurred, and your code can then respond accordingly.
There are several ways to do this:
- You can use the PlayerInput component to set up callbacks in the inspector.
- Each Action has a
started
,performed
, andcanceled
callback. - Each Action Map has an
actionTriggered
callback. - The Input System has a global
InputSystem.onActionChange
callback. InputActionTrace
can record changes happening on Actions.
The PlayerInput component
The PlayerInput component is the simplest way to set up Action callbacks. It provides an interface in the inspector that allows you set up callbacks directly to your methods without requiring intermediate code. Read more about the PlayerInput component.
Alternatively, you can implement callbacks entirely from your own code using the following workflow:
Action callbacks
Every Action has a set of distinct phases it can go through in response to receiving input.
Phase | Description |
---|---|
Disabled |
The Action is disabled and can't receive input. |
Waiting |
The Action is enabled and is actively waiting for input. |
Started |
The Input System has received input that started an Interaction with the Action. |
Performed |
An Interaction with the Action has been completed. |
Canceled |
An Interaction with the Action has been canceled. |
You can read the current phase of an action using InputAction.phase
.
The Started
, Performed
, and Canceled
phases each have a callback associated with them:
var action = new InputAction();
action.started += context => /* Action was started */;
action.performed += context => /* Action was performed */;
action.canceled += context => /* Action was canceled */;
Each callback receives an InputAction.CallbackContext
structure, which holds context information that you can use to query the current state of the Action and to read out values from Controls that triggered the Action (InputAction.CallbackContext.ReadValue
).
Note
: The contents of the structure are only valid for the duration of the callback. In particular, it isn't safe to store the received context and later access its properties from outside the callback.
When and how the callbacks are triggered depends on the Interactions present on the respective Bindings. If the Bindings have no Interactions that apply to them, the default Interaction applies.
InputActionMap.actionTriggered
callback
Instead of listening to individual actions, you can listen on an entire Action Map for state changes on any of the Actions in the Action Map.
var actionMap = new InputActionMap();
actionMap.AddAction("action1", "<Gamepad>/buttonSouth");
actionMap.AddAction("action2", "<Gamepad>/buttonNorth");
actionMap.actionTriggered +=
context => { ... };
The argument received is the same InputAction.CallbackContext
structure that you receive through the started
, performed
, and canceled
callbacks.
Note
: The Input System calls
InputActionMap.actionTriggered
for all three of the individual callbacks on Actions. That is, you getstarted
,performed
, andcanceled
all on a single callback.
InputSystem.onActionChange
callback
Similar to InputSystem.onDeviceChange
, your app can listen for any action-related change globally.
InputSystem.onActionChange +=
(obj, change) =>
{
// obj can be either an InputAction or an InputActionMap
// depending on the specific change.
switch (change)
{
case InputActionChange.ActionStarted:
case InputActionChange.ActionPerformed:
case InputActionChange.ActionCanceled:
Debug.Log($"{((InputAction)obj).name} {change}");
break;
}
}
InputActionTrace
You can trace Actions to generate a log of all activity that happened on a particular set of Actions. To do so, use InputActionTrace
. This behaves in a similar way to InputEventTrace
for events.
Note
:
InputActionTrace
allocates unmanaged memory and needs to be disposed of so that it doesn't create memory leaks.
var trace = new InputActionTrace();
// Subscribe trace to single Action.
// (Use UnsubscribeFrom to unsubscribe)
trace.SubscribeTo(myAction);
// Subscribe trace to entire Action Map.
// (Use UnsubscribeFrom to unsubscribe)
trace.SubscribeTo(myActionMap);
// Subscribe trace to all Actions in the system.
trace.SubscribeToAll();
// Record a single triggering of an Action.
myAction.performed +=
ctx =>
{
if (ctx.ReadValue<float>() > 0.5f)
trace.RecordAction(ctx);
};
// Output trace to console.
Debug.Log(string.Join(",\n", trace));
// Walk through all recorded Actions and then clear trace.
foreach (var record in trace)
{
Debug.Log($"{record.action} was {record.phase} by control {record.control}");
// To read out the value, you either have to know the value type or read the
// value out as a generic byte buffer. Here, we assume that the value type is
// float.
Debug.Log("Value: " + record.ReadValue<float>());
// If it's okay to accept a GC hit, you can also read out values as objects.
// In this case, you don't have to know the value type.
Debug.Log("Value: " + record.ReadValueAsObject());
}
trace.Clear();
// Unsubscribe trace from everything.
trace.UnsubscribeFromAll();
// Release memory held by trace.
trace.Dispose();
Once recorded, a trace can be safely read from multiple threads as long as it is not concurrently being written to and as long as the Action setup (that is, the configuration data accessed by the trace) is not concurrently being changed on the main thread.
Action types
Each Action can be one of three different Action types. You can select the Action type in the Input Action editor window, or by specifying the type
parameter when calling the InputAction()
constructor. The Action type influences how the Input System processes state changes for the Action. The default Action type is Value
.
Value
This is the default Action type. Use this for any inputs which should track continuous changes to the state of a Control.
Value
type actions continuously monitor all the Controls which are bound to the Action, and then choose the one which is the most actuated to be the Control driving the Action, and report the values from that Control in callbacks, triggered whenever the value changes. If a different bound Control actuated more, then that Control becomes the Control driving the Action, and the Action starts reporting values from that Control. This process is called conflict resolution. This is useful if you want to allow different Controls to control an Action in the game, but only take input from one Control at the same time.
When the Action initially enables, it performs an initial state check of all bound Controls. If any of them is actuated, the Action then triggers a callback with the current value.
Button
This is very similar to Value
, but Button
type Actions can only be bound to ButtonControl
Controls, and don't perform an initial state check like Value
Actions do (see the Value section above). Use this for inputs that trigger an Action once every time they are pressed. The initial state check is usually not useful in such cases, because it can trigger actions if the button is still held down from a previous press when the Action was enabled.
Pass-Through
Pass-Through
Actions bypass the conflict resolution process described above for Value
Actions and don't use the concept of a specific Control driving the Action. Instead, any change to any bound Control triggers a callback with that Control's value. This is useful if you want to process all input from a set of Controls.
Debugging Actions
To see currently enabled Actions and their bound Controls, use the Input Debugger.
You can also use the InputActionVisualizer
component from the Visualizers sample to get an on-screen visualization of an Action's value and Interaction state in real-time.
Using Actions with multiple players
You can use the same Action definitions for multiple local players (for example, in a local co-op game). For more information, see documentation on the Player Input Manager component.