动作 (Actions) 允许您将输入的逻辑含义(例如玩家在游戏或应用程序中可以做的事情,如移动、跳跃、蹲下)与设备特定的控件(例如按下按钮或移动游戏手柄摇杆)分开。
如果不使用动作,输入的含义和设备控件最终会被硬编码在您的脚本中,这虽然实现起来很快,但不够灵活,因此不总是理想的。例如,以下代码将 moveVector
变量硬编码为从游戏手柄的右摇杆读取值:
使用动作后,您不需要在代码中直接引用特定设备或其控件。相反,动作的绑定定义了哪个设备的哪些控件用于执行动作,您的代码变得更加简单。在下面的示例中,moveAction
包含对动作的引用,这个动作可以在代码中定义,也可以在动作资产中定义:
然后,您可以使用可视化编辑器(在检查器或动作资产编辑器中)来建立动作与一个或多个设备控件之间的映射。例如,在下图中,“移动”动作绑定到了游戏手柄的左摇杆和键盘的箭头键。
这还使得玩家可以在运行时更轻松地自定义绑定。
注意:动作是仅在游戏时可用的功能。您不能在 EditorWindow
代码中使用它们。
有关本页面中使用的术语和定义,请参阅 概念 (Concepts)。
在 API 中,与动作相关的三个关键类是:
类 描述
InputActionAsset 包含一个或多个动作映射 (Action Maps) 的资产,可能还包括一系列控制方案 (Control Schemes)。有关如何创建、编辑和使用这些资产的更多信息,请参阅 动作资产 (Action Assets)。
InputActionMap 动作的命名集合。
InputAction 一个命名的动作,响应输入时触发回调。
动作使用 InputBinding
来引用它们收集的输入。有关绑定和如何使用它们的更多信息,请参阅 动作绑定 (Action Bindings)。
每个动作都有一个名称 (InputAction.name
),在其所属的动作映射 (InputAction.actionMap
) 中必须是唯一的。如果该动作属于某个动作映射的话(见 InputAction.actionMap
)。每个动作也有一个唯一的ID (InputAction.id
),您可以使用该ID来引用该动作。即使重命名动作,ID 也保持不变。
每个动作映射都有一个名称 (InputActionMap.name
),在其所属的动作资产 (InputActionMap.asset
) 中必须是唯一的。如果该动作映射属于某个动作资产的话(见 InputActionMap.asset
)。每个动作映射也有一个唯一的ID (InputActionMap.id
),您可以使用该ID来引用该动作映射。即使重命名动作映射,ID 也保持不变。
您可以通过以下几种方式创建动作:
使用输入动作资产 (Input Action Assets) 的专用编辑器。
将它们嵌入到 MonoBehaviour
组件中,然后在检查器中设置绑定。
从 JSON 中手动加载它们。
完全在代码中创建它们,包括设置绑定。
有关如何在专用编辑器中创建和编辑输入动作资产的更多信息,请参阅 动作资产 (Action Assets)。如果您希望将所有输入动作和绑定组织到一个资产中,这是推荐的工作流程,这在许多类型的游戏或应用程序中经常是这样做的。
在 MonoBehaviour
中嵌入动作
作为使用动作资产的替代方法,您可以将单个输入动作和输入动作映射直接嵌入到 MonoBehaviour
组件中的字段内,如下所示:
结果类似于使用动作资产,不同之处在于这些动作在 GameObject 的属性中定义,并作为场景或预制数据保存,而不是在专用资产中。
当您在 MonoBehaviour
中嵌入动作并将该 MonoBehaviour
分配给一个 GameObject 时,GameObject 的检查器窗口将显示一个类似于动作资产窗口的界面,允许您设置这些动作的绑定。例如:
可视化编辑器的工作方式类似于动作资产编辑器。
要添加或删除动作或绑定,请单击标题中的添加 (+) 或删除 (-) 图标。
要编辑绑定,请双击它们。
要编辑动作,请在动作映射中双击它们,或单击单个动作属性上的齿轮图标。
您还可以右键单击条目以调出上下文菜单,并且可以拖动它们。按住 Alt 键并拖动条目可以复制它。
嵌入到 MonoBehaviour
组件中的动作和动作映射必须手动启用和禁用。
您可以以动作映射集的形式或作为完整的 InputActionAsset
从 JSON 中加载动作。这在运行时的 Player 中也有效。
在代码中创建动作
您可以完全在代码中手动创建和配置动作,包括分配绑定。这在运行时的 Player 中也有效。例如:
以这种方式在播放模式下创建的任何动作在退出播放模式后不会保存在 Input Action Asset
中。这意味着您可以在编辑器中以现实的方式测试应用程序,而不必担心意外修改资产。
输入系统包附带了一个名为 DefaultInputActions.inputactions
的资产,其中包含默认的动作设置。您可以像使用其他 Unity 资产一样在项目中直接引用此资产。不过,通过 DefaultInputActions
类,代码形式的资产也可用。
使用动作 (Using Actions)
要让动作执行某些操作,您必须先启用它。您可以通过单独启用动作或通过动作映射批量启用它们来实现。在所有情况下,第二种方法更为高效。
启用动作时,输入系统会解析其绑定,除非它已经这样做,或者动作可以使用的设备集合未发生变化。有关此过程的更多详细信息,请参阅绑定解析的文档。
在启用状态下,动作会主动监视与其绑定的控件。如果绑定的控件发生状态变化,动作会处理这种变化。如果控件的变化表示交互的变化,动作会创建一个响应。所有这些都会在输入系统更新逻辑期间发生。根据输入设置中选择的更新模式,这个过程会在每帧、每次固定更新或手动更新(如果更新设置为手动)时发生。
动作本身并不代表对输入的实际响应。相反,动作通知您的代码某种类型的输入已发生,您的代码随后会对此信息做出响应。
有几种方式可以实现这一点:
每个动作都有一个 started
、performed
和 canceled
回调。
每个动作映射都有一个 actionTriggered
回调。
输入系统有一个全局的 InputSystem.onActionChange
回调。
您可以在需要时随时轮询动作的当前状态。
InputActionTrace
可以记录动作上发生的变化。
此外,还有两种更高级、更简化的方式来从动作中获取输入:使用 PlayerInput
,或生成包装输入动作的脚本代码。
每个动作都有一组不同的阶段,响应输入时会经历这些阶段。
阶段 描述
Disabled
动作被禁用,无法接收输入。
Waiting
动作已启用,正在等待输入。
Started
输入系统已接收到启动与动作交互的输入。
Performed
与动作的交互已完成。
Canceled
与动作的交互已取消。
您可以使用 InputAction.phase
读取动作的当前阶段。
Started
、Performed
和 Canceled
阶段各有一个回调与之关联:
每个回调都会接收一个 InputAction.CallbackContext
结构,该结构包含的上下文信息可用于查询动作的当前状态,并从触发动作的控件中读取值(InputAction.CallbackContext.ReadValue
)。
注意:结构的内容仅在回调的持续时间内有效。特别是,存储接收到的上下文并在回调外部访问其属性是不安全的。
回调的触发时间和方式取决于相关绑定上存在的交互。如果绑定上没有适用的交互,则应用默认交互。
InputActionMap.actionTriggered
回调与监听单个动作相比,您可以监听整个动作映射中的所有动作的状态变化。
接收到的参数与通过 started
、performed
和 canceled
回调接收到的 InputAction.CallbackContext
结构相同。
注意:输入系统为动作上的所有三个单独回调调用 InputActionMap.actionTriggered
。也就是说,您会在单个回调中接收到 started
、performed
和 canceled
。
InputSystem.onActionChange
回调与 InputSystem.onDeviceChange
类似,您的应用程序可以全局监听任何与动作相关的变化。
轮询动作 (Polling Actions)
有时,轮询动作的值比使用回调更简单。
您可以使用 InputAction.ReadValue<>()
轮询动作的当前值:
请注意,值类型必须与从中读取值的控件的值类型相对应。
要确定动作是否在当前帧中执行,可以使用 InputAction.WasPerformedThisFrame()
:
最后,有三种方法可以轮询按键按下和释放:
方法描述
InputAction.IsPressed()
如果动作的激活水平已超过按下点,并且尚未下降到或低于释放阈值,则为 True。
InputAction.WasPressedThisFrame()
如果动作的激活水平在当前帧的任何时候达到或超过按下点,则为 True。
InputAction.WasReleasedThisFrame()
如果动作的激活水平在当前帧的任何时候从按下点或以上水平下降到或低于释放阈值,则为 True。
示例:
InputActionTrace
您可以跟踪动作以生成特定动作集上发生的所有活动的日志。为此,请使用 InputActionTrace
。这类似于 InputEventTrace
对事件的行为。
注意:InputActionTrace
会分配非托管内存,需要进行处理以避免内存泄漏。
一旦记录了跟踪,只要它没有同时被写入,并且在主线程上没有同时更改动作设置(即跟踪访问的配置数据),就可以安全地从多个线程读取。
每个动作可以是三种不同动作类型之一。您可以在输入动作编辑器窗口中选择动作类型,或通过调用 InputAction()
构造函数时指定类型参数。动作类型影响输入系统处理动作状态变化的方式。默认的动作类型是 Value
。
Value
这是默认的动作类型。用于跟踪控件状态持续变化的任何输入。
Value
类型的动作会持续监控绑定到动作的所有控件,然后选择其中激活最多的控件作为驱动动作的控件,并在回调中报告该控件的值,当值发生变化时触发回调。如果另一个绑定控件激活更多,那么该控件将成为驱动动作的控件,动作开始报告该控件的值。这个过程称为冲突解决。如果您希望允许不同的控件控制游戏中的动作,但一次只接受一个控件的输入,这非常有用。
当动作最初启用时,它会对所有绑定控件执行初始状态检查。如果其中任何一个控件处于激活状态,动作将触发一个包含当前值的回调。
Button
这与 Value
非常相似,但 Button
类型的动作只能绑定到 ButtonControl
控件,不会像 Value
动作那样执行初始状态检查(见上面的 Value
部分)。使用此类型的动作可以在每次按下时触发动作。初始状态检查通常在这种情况下没有用,因为如果动作启用时按钮仍然按下,可能会触发动作。
Pass-Through
Pass-Through
动作绕过了上面描述的 Value
动作的冲突解决过程,不使用驱动动作的特定控件的概念。相反,对任何绑定控件的任何更改都会触发一个包含该控件值的回调。如果您希望处理来自一组控件的所有输入,这非常有用。
要查看当前启用的动作及其绑定的控件,请使用 Input Debugger
。
您还可以使用来自 Visualizers
示例的 InputActionVisualizer
组件来实时显示动作的值和交互状态。
您可以为多个本地玩家使用相同的动作定义(例如,在本地合作游戏中)。有关详细信息,请参阅 Player Input Manager 组件的文档。