UnityGame/Library/PackageCache/com.unity.inputsystem/Documentation~/RespondingToActions.md
2024-10-27 10:53:47 +03:00

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 a MonoBehaviour 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:

  1. You can use the PlayerInput component to set up callbacks in the inspector.
  2. Each Action has a started, performed, and canceled callback.
  3. Each Action Map has an actionTriggered callback.
  4. The Input System has a global InputSystem.onActionChange callback.
  5. 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 get started, performed, and canceled 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.