IndieGame/client/Packages/com.unity.inputsystem@1.7.0/Documentation~/Actions.md
DOBEST\zhaoyingjie f242607587 初始化工程
2024-10-11 10:12:15 +08:00

25 KiB

uid
input-system-actions

Actions

Related pages:

Actions allow you to separate the logical meaning of an input (the things your user can do in your game or app, such as move, jump, crouch) and the device-specific controls (for example, pressing a button or moving a gamepad stick).

Without Actions, the meaning of your input and the device controls end up hard-coded together in your scripts, which although quick to implement, is not very flexible and therefore not always desirable. For example, here the moveVector variable is hard-coded to read values from the right stick of a gamepad:

    // without Actions, you end up hard-coding device controls
    // like this: (not always desirable)
    moveVector = gamepad.rightStick.ReadValue<Vector2>();

When you use Actions, you do not need to refer to specific devices or their controls in your code. Instead the Action's binding defines which controls on which device is used to perform the action, and your code becomes simpler. In this example, moveAction would contain a reference to an Action, defined either in code, or in an Action Asset:

    // using Actions removes the need to refer directly to device controls
    moveAmount = moveAction.ReadValue<Vector2>();

You can then use the visual editor (either in the inspector, or in the Actions Asset editor) to establish the mapping between the Action and one or more device controls. For example in this screenshot, the "move" action is bound to the left gamepad stick, and the keyboard's arrow keys.

Actions Bindings

This also makes it easier to let players customize bindings at runtime.

!Note

  • Actions are a game-time only feature. You can't use them in EditorWindow code.
  • For an overview of the terms and terminology used on this page, see Concepts.

Overview

There are three key classes for Actions in the API:

Class Description
InputActionAsset An Asset that contains one or more Action Maps and, optionally, a sequence of Control Schemes. For more information on how to create, edit, and work with these Assets, see Action Assets.
InputActionMap A named collection of Actions.
InputAction A named Action that triggers callbacks in response to input.

Actions use InputBinding to refer to the inputs they collect. For more information about Bindings and how to use them, see Action Bindings.

Each Action has a name (InputAction.name), which must be unique within the Action Map that the Action belongs to, if any (see InputAction.actionMap). Each Action also has a unique ID (InputAction.id), which you can use to reference the Action. The ID remains the same even if you rename the Action.

Each Action Map has a name (InputActionMap.name), which must be unique within the Action Asset that the Action Map belongs to, if any (see InputActionMap.asset). Each Action Map also has a unique ID (InputActionMap.id), which you can use to reference the Action Map. The ID remains the same even if you rename the Action Map.

Creating Actions

You can create Actions in any of the following ways:

  • Use the dedicated editor for Input Action Assets.
  • Embed them in MonoBehaviour components, then set up bindings in the Inspector.
  • Manually load them from JSON.
  • Create them entirely in code, including setting up the bindings.

Creating Actions using the Action editor

For information on how to create and edit Input Action Assets in the dedicated editor, see Action Assets. This is the recommended workflow if you want to organise all your input actions and bindings together into a single Asset, which is often the case for many types of game or app.

Action Editor Window

Creating Actions by embedding them in MonoBehaviours

As an alternative to using an Action Asset, You can embed individual Input Action and Input Action Maps as fields directly inside MonoBehaviour components, like this:

using UnityEngine;
using UnityEngine.InputSystem;

public class ExampleScript : MonoBehaviour
{
    public InputAction move;
    public InputAction jump;
}

The result is similar to using an Action Asset, except the Actions are defined in the GameObject's properties and saved as Scene or Prefab data, instead of in a dedicated Asset.

When you embed actions in a MonoBehaviour and assign that MonoBehaviour to a GameObject, the GameObject's Inspector window displays an interface similar to the Actions Asset window, which allows you to set up the bindings for those actions. For example:

MyBehavior Inspector

The visual editors work similarly to the Action Asset editor.

  • To add or remove Actions or Bindings, click the Add (+) or Remove (-) icon in the header.
  • To edit Bindings, double-click them.
    InputBinding Inspector
  • To edit Actions, double-click them in an Action Map, or click the gear icon on individual Action properties.
    InputAction Inspector
  • You can also right-click entries to bring up a context menu, and you can drag them. Hold the Alt key and drag an entry to duplicate it.

You must manually enable and disable Actions and Action Maps that are embedded in MonoBehaviour components.

public class MyBehavior : MonoBehaviour
{
    // ...

    void Awake()
    {
        fireAction.performed += OnFire;
        lookAction.performed += OnLook;

        gameplayActions["fire"].performed += OnFire;
    }

    void OnEnable()
    {
        fireAction.Enable();
        lookAction.Enable();

        gameplayActions.Enable();
    }

    void OnDisable()
    {
        fireAction.Disable();
        lookAction.Disable();

        gameplayActions.Disable();
    }
}

Loading Actions from JSON

You can load Actions as JSON in the form of a set of Action Maps or as a full InputActionAsset. This also works at runtime in the Player.

// Load a set of action maps from JSON.
var maps = InputActionMap.FromJson(json);

// Load an entire InputActionAsset from JSON.
var asset = InputActionAsset.FromJson(json);

Creating Actions in code

You can manually create and configure Actions entirely in code, including assigning the bindings. This also works at runtime in the Player. For example:

// Create free-standing Actions.
var lookAction = new InputAction("look", binding: "<Gamepad>/leftStick");
var moveAction = new InputAction("move", binding: "<Gamepad>/rightStick");

lookAction.AddBinding("<Mouse>/delta");
moveAction.AddCompositeBinding("Dpad")
    .With("Up", "<Keyboard>/w")
    .With("Down", "<Keyboard>/s")
    .With("Left", "<Keyboard>/a")
    .With("Right", "<Keyboard>/d");

// Create an Action Map with Actions.
var map = new InputActionMap("Gameplay");
var lookAction = map.AddAction("look");
lookAction.AddBinding("<Gamepad>/leftStick");

// Create an Action Asset.
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
var gameplayMap = new InputActionMap("gameplay");
asset.AddActionMap(gameplayMap);
var lookAction = gameplayMap.AddAction("look", "<Gamepad>/leftStick");

Any action that you create in this way during Play mode do not persist in the Input Action Asset after you exit Play mode. This means you can test your application in a realistic manner in the Editor without having to worry about inadvertently modifying the asset.

Default Actions

An asset called DefaultInputActions.inputactions containing a default setup of Actions comes with the Input System Package. You can reference this asset directly in your projects like any other Unity asset. However, the asset is also available in code form through the DefaultInputActions class.

void Start()
{
    // Create an instance of the default actions.
    var actions = new DefaultInputActions();
    actions.Player.Look.performed += OnLook;
    actions.Player.Move.performed += OnMove;
    actions.Enable();
}

Using Actions

For an Action to do something, you must first enable it. You can do this either by individually enabling Actions, or by enabling them in bulk through Action Maps. The second method is more efficient in all scenarios.

// Enable a single action.
lookAction.Enable();

// Enable an en entire action map.
gameplayActions.Enable();

When you enable an Action, the Input System resolves its bindings, unless it has done so already, or if the set of devices that the Action can use has not changed. For more details about this process, see the documentation on binding resolution.

You can't change certain aspects of the configuration, such Action Bindings, while an Action is enabled. To stop Actions or Action Maps from responding to input, call Disable.

While enabled, an Action actively monitors the Control(s) it's bound to. If a bound Control changes state, the Action processes the change. If the Control's change represents an Interaction change, the Action creates a response. All of this happens during the Input System update logic. Depending on the update mode selected in the input settings, this happens once every frame, once every fixed update, or manually if updates are set to manual.

Responding to Actions

An Action doesn't represent an actual response to input by itself. Instead, an Action informs your code that a certain type of input has occurred. Your code then responds to this information.

There are several ways to do this:

  1. Each Action has a started, performed, and canceled callback.
  2. Each Action Map has an actionTriggered callback.
  3. The Input System has a global InputSystem.onActionChange callback.
  4. You can poll the current state of an Action whenever you need it.
  5. InputActionTrace can record changes happening on Actions.

There are also two higher-level, more streamlined ways of picking up input from Actions: use PlayerInput, or generate script code that wraps around the Input Actions.

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 += ctx => /* Action was started */;
    action.performed += ctx => /* Action was performed */;
    action.canceled += ctx => /* 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;
        }
    }

Polling Actions

Instead of using callbacks, it might be simpler sometimes to poll the value of an Action where you need it in your code.

You can poll the current value of an Action using InputAction.ReadValue<>():

    public InputAction moveAction;
    public float moveSpeed = 10.0f;
    public Vector2 position;

    void Start()
    {
        moveAction.Enable();
    }

    void Update()
    {
        var moveDirection = moveAction.ReadValue<Vector2>();
        position += moveDirection * moveSpeed * Time.deltaTime;
    }

Note that the value type has to correspond to the value type of the control that the value is being read from.

To determine whether an action was performed in the current frame, you can use InputAction.WasPerformedThisFrame():

    private InputAction action;

    void Start()
    {
        // Set up an action that triggers when the A button is
        // held for 1 second.
        action = new InputAction(
            type: InputActionType.Button,
            binding: "<Gamepad>/buttonSouth",
            interactions: "hold(duration=1)");

        action.Enable();
    }

    void Update()
    {
        if (action.WasPerformedThisFrame())
            Debug.Log("A button on gamepad was held for one second");
    }

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.

Example:

    public PlayerInput playerInput;

    public void Update()
    {
        // IsPressed
        if (playerInput.actions["up"].IsPressed())
            transform.Translate(0, 10 * Time.deltaTime, 0);

        // WasPressedThisFrame
        if (playerInput.actions["teleport"].WasPressedThisFrame())
            Teleport();

        // WasReleasedThisFrame
        if (playerInput.actions["submit"].WasReleasedThisFrame())
            ConfirmSelection();
    }

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.