Unity Input System 入门指南:从核心概念到实战应用

📅 2026/7/11 8:51:58 👁️ 阅读次数 📝 编程学习
Unity Input System 入门指南:从核心概念到实战应用

1. 项目概述:为什么你需要拥抱新输入系统?

如果你还在用Unity老旧的Input Manager,每次处理手柄、键盘、触摸屏的输入都要写一堆if-else,或者被跨平台输入适配搞得焦头烂额,那么是时候了解一下Unity的Input System了。这不仅仅是Unity官方推出的一个“新”输入系统,它更代表了一种现代化、声明式、事件驱动的输入处理哲学。我最初接触它时,也被其看似复杂的设置劝退过,但真正上手后,发现它带来的开发效率和代码整洁度提升是革命性的。简单来说,它把输入从“轮询按键状态”变成了“响应输入事件”,并且用一个统一的抽象层(Input Actions)来管理所有输入设备,无论是PC、主机、手机还是VR设备。

这个教程的目标,就是带你从零开始,避开我踩过的所有坑,一步步将Input System集成到你的项目中,并完成一个包含角色移动、攻击、UI交互的实战案例。我们会涵盖从Package Manager安装、基础配置、Action Assets创建,到脚本编写、复杂输入处理(如组合键、长按、双击),再到打包发布时可能遇到的“坑”及其解决方案。无论你是刚接触Unity的新手,还是想从旧系统迁移的老鸟,这篇“保姆级”指南都会让你感觉像有个老手在旁边手把手教学。

2. 环境准备与Input System安装

2.1 安装前的版本确认与注意事项

在动手安装之前,有一个至关重要的前提:确认你的Unity编辑器版本。Input System作为一个较新的包,对Unity版本有最低要求。官方推荐使用Unity 2019.4 LTS或更高版本,并且强烈建议使用2020.3 LTS或2021.3 LTS等长期支持版以获得最佳稳定性和功能支持。我个人的主力开发环境是Unity 2021.3 LTS,这个版本下Input System的兼容性和功能完整性都经过了充分验证。

注意:如果你正在维护一个使用旧版Input Manager的大型项目,直接切换输入系统可能会导致现有输入代码全部失效。Unity提供了兼容模式,但最佳实践是在一个新项目或专门的原型场景中先行学习和测试,待完全掌握后再考虑迁移。

2.2 通过Package Manager安装Input System

安装过程本身非常简单,但有几个细节点需要留意,这些细节往往决定了后续配置是否顺利。

  1. 打开Package Manager:在Unity编辑器顶部菜单栏,依次点击Window->Package Manager
  2. 切换资源库:在Package Manager窗口的左上角,你会看到一个下拉菜单,默认可能是“Packages: Unity Registry”或“Packages: My Assets”。请确保它被设置为“Unity Registry”。这是从Unity官方包服务器获取包的必要步骤。
  3. 搜索与安装:在搜索栏(通常位于窗口右上角)中输入“Input System”。在搜索结果列表中,你应该能看到一个由“Unity Technologies”发布的名为“Input System”的包。点击它,在右侧详情面板中,你会看到“Install”按钮。直接点击安装。

安装过程中,Unity会弹出一个警告对话框,提示你启用新的输入后端并禁用旧的。这里务必点击“Yes”。这个操作会修改项目的Player Settings,将Active Input Handling从“Input Manager (Old)”或“Both”切换为“Input System Package (New)”。如果你错过了这个对话框,或者想手动修改,可以前往Edit->Project Settings->Player,在“Other Settings”部分找到“Active Input Handling”选项进行设置。

实操心得:安装后第一次重启编辑器或重新加载项目时,可能会感觉编辑器略有卡顿,这是正常现象,因为Unity正在后台处理输入系统的初始化。另外,安装Input System后,所有之前依赖于旧InputAPI的代码(如Input.GetKey,Input.GetAxis)在编译时都会报错,除非你启用了“Both”模式。这正是我们转向新API的开始。

3. 核心概念解析:Actions, Action Maps, Bindings

这是理解Input System的基石。很多初学者觉得它复杂,就是因为没理清这三者的关系。我们可以用一个“游戏控制器”的类比来理解:

  • Input Action Asset (.inputactions 文件):这就是你的整个游戏手柄。它是一个.asset文件,用于集中管理你游戏的所有输入配置。你通常会为整个项目创建一个,或者按功能模块(如“玩家”、“UI”、“调试”)创建多个。
  • Action Map(动作映射集):这是手柄上的模式切换开关。比如,一个叫“Player”的Action Map管理所有游戏内角色控制的输入(移动、跳跃、攻击);另一个叫“UI”的Action Map则管理菜单导航、确认、取消等输入。同一时间,通常只有一个Action Map是启用的,这避免了输入冲突。
  • Action(动作):这是手柄上的每一个具体按钮或摇杆的功能定义。例如,在“Player”这个Action Map里,你可以定义“Move”(移动)、“Jump”(跳跃)、“Fire”(开火)等Action。Action本身不关心是哪个物理按键触发的,它只定义“要做什么”。
  • Binding(绑定):这才是将物理输入设备(键盘按键、鼠标点击、手柄摇杆)映射到具体Action的规则。例如,“Move”这个Action可以绑定到键盘的WASD键,也可以同时绑定到手柄的左摇杆。“Jump”可以绑定到键盘空格键和手柄的A键。

这种设计的巨大优势在于解耦。你的游戏逻辑代码只响应“Jump”这个Action被触发,而完全不用关心玩家实际按的是空格键、手柄A键还是屏幕上的一个虚拟按钮。未来要添加新的输入设备(如方向盘、体感设备),你只需要在Input Action Asset中添加新的Bindings,而无需修改任何游戏逻辑代码。

3.1 创建你的第一个Input Action Asset

理论说再多不如动手操作。让我们来创建一个:

  1. 在Project窗口右键,选择Create->Input Actions。我给文件命名为“PlayerControls”。
  2. 双击这个新创建的.inputactions文件,会打开一个专门的Input Actions编辑器窗口

这个窗口就是你的主战场。默认会有一个名为“New action map”的Action Map,将其重命名为“Player”。然后,我们开始添加Action。

3.2 定义基础Action:移动、跳跃与攻击

在“Player”这个Action Map下,点击“+”号添加Action。

  1. Move (2D向量类型)

    • Action Type:选择Value。这表示该输入会提供一个连续的值,如摇杆的倾斜度。
    • Control Type:选择Vector 2。因为移动通常有X(左右)和Y(前后)两个维度。
    • 在Bindings列表下,点击“+”为它添加绑定。选择<Gamepad>/leftStick。这样,手柄左摇杆就绑定了。
    • 再次点击“+”,选择<Keyboard>/w,然后不要直接完成,而是点击这个绑定右侧的“+”号,为其添加复合路径(Composite)。选择“2D Vector Composite”。你会发现它自动生成了Up(W)、Down(S)、Left(A)、Right(D)的绑定。这是为键盘WASD移动的标准配置。
  2. Jump(按钮类型)

    • Action Type:选择Button。这表示一个瞬时的按下/松开动作。
    • 添加绑定:<Keyboard>/space<Gamepad>/buttonSouth(通常是A键或X键,取决于手柄布局)。
  3. Fire(按钮类型)

    • Action TypeButton
    • 添加绑定:<Mouse>/leftButton<Gamepad>/rightTrigger(右扳机键)。

注意事项:在绑定键盘按键时,系统可能会显示如“W [Keyboard]”的格式。<Keyboard>是一个设备路径,它指代整个键盘设备。这种基于路径的绑定方式极其强大,可以精确指定到特定厂商的特定设备。

4. 在脚本中启用与响应输入

Asset配置好了,接下来就是让它在代码里活起来。

4.1 生成C#脚本包装类(强烈推荐)

这是Input System一个超级省心的功能!在Input Actions编辑器窗口的右上角,找到“Generate C# Class”这个复选框,勾选它。然后点击窗口左上角的“Save Asset”按钮。

Unity会自动在.inputactions文件所在的目录下,生成一个同名的C#脚本(如PlayerControls.cs)。这个脚本包含了一个与你的Asset结构完全对应的类,里面预定义了所有Action Map和Action的引用,以及一系列事件回调(如started,performed,canceled)。使用这个自动生成的类,可以避免手动查找Action字符串导致的拼写错误,并且有完整的代码提示。

4.2 编写玩家控制器脚本

我们来创建一个最简单的角色移动脚本。

using UnityEngine; using UnityEngine.InputSystem; // 关键命名空间 // 使用自动生成的包装类,类名可能因你的文件名而异 [RequireComponent(typeof(CharacterController))] public class PlayerController : MonoBehaviour { public float moveSpeed = 5f; private CharacterController _controller; private Vector2 _moveInput; // 存储移动输入值 private PlayerControls _inputActions; // 自动生成类的实例 private void Awake() { _controller = GetComponent<CharacterController>(); _inputActions = new PlayerControls(); // 实例化输入控制类 } private void OnEnable() { // 启用特定的Action Map,并订阅事件 _inputActions.Player.Enable(); _inputActions.Player.Move.performed += OnMovePerformed; _inputActions.Player.Move.canceled += OnMoveCanceled; _inputActions.Player.Jump.performed += OnJumpPerformed; } private void OnDisable() { // 禁用时,务必取消订阅并禁用Action Map,防止内存泄漏和意外输入 _inputActions.Player.Move.performed -= OnMovePerformed; _inputActions.Player.Move.canceled -= OnMoveCanceled; _inputActions.Player.Jump.performed -= OnJumpPerformed; _inputActions.Player.Disable(); } // 当移动输入发生时(如按下W或推动摇杆) private void OnMovePerformed(InputAction.CallbackContext context) { _moveInput = context.ReadValue<Vector2>(); } // 当移动输入取消时(如松开所有键或摇杆回中) private void OnMoveCanceled(InputAction.CallbackContext context) { _moveInput = Vector2.zero; } // 当跳跃按钮被按下时 private void OnJumpPerformed(InputAction.CallbackContext context) { // 这里实现跳跃逻辑,例如给角色一个向上的速度 Debug.Log("Jump!"); // 简单的跳跃示例:假设有一个向上的速度 // _verticalVelocity = jumpForce; } private void Update() { // 在Update中应用持续移动 Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y); // 将输入方向从本地坐标系转换到世界坐标系 move = transform.TransformDirection(move); _controller.Move(move * moveSpeed * Time.deltaTime); } }

代码解析与心得

  • InputAction.CallbackContext context:这个参数是黄金信息源。通过context.ReadValue<T>()可以读取输入值(如Vector2摇杆量、float扳机压力)。通过context.phase可以知道输入处于开始(Started)、进行中(Performed)还是取消(Canceled)阶段。
  • OnEnable/OnDisable配对:这是重中之重。务必在脚本启用时订阅事件并启用Action Map,在脚本禁用或物体销毁时取消订阅并禁用Action Map。忘记这一步是导致“幽灵输入”(对象已禁用但仍在接收输入)和内存泄漏的常见原因。
  • 性能考量:对于像“移动”这样的连续值输入,我们在performed事件中缓存输入向量,在Update中使用。对于“跳跃”这种瞬时动作,直接在performed事件中处理逻辑即可。

5. 高级输入处理与交互技巧

掌握了基础,我们来点更实用的,处理那些让游戏手感变好的细节。

5.1 处理组合输入(长按、双击、按住)

Input System内置了强大的交互器(Interactions)功能,无需自己写计时器逻辑。

在Input Actions编辑器中,点击任意一个Binding(比如Jump绑定到空格键的那一行),在右侧的“Inspector”面板中,你会看到“Interactions”字段。点击“+”可以添加交互。

  • 长按(Hold):添加一个“Hold”交互。可以设置Duration(按住多久才触发)和Press Point(触发阈值)。适合用于“蓄力攻击”或“打开武器轮盘”。
  • 双击(Tap):添加一个“Tap”交互,然后设置Tap Time(两次点击的最大间隔)。适合“冲刺”或“特殊技能”。
  • 多击(Multi Tap):可以设置点击次数。
  • 慢按(Slow Tap)按压(Press)等。

添加交互后,在脚本中你可以通过检查context.phase来区分不同的交互结果。例如,一个绑定了“Hold”交互的Action,其performed事件只会在按住指定时长后触发一次。

5.2 输入处理(死区、缩放、轴反转)

还是在Binding的Inspector面板,找到“Processors”(处理器)。这是对原始输入数据进行加工的流水线。

  • Stick Deadzone(摇杆死区):摇杆在中心位置常有微小漂移,死区可以忽略这个范围内的输入。MinMax定义了死区的形状和范围,一般用默认的“轴向(Axis)”死区即可。
  • Scale Vector2/Vector3(缩放):可以倍增输入值。比如把手柄摇杆的输入值(范围-1到1)乘以2,让角色移动更灵敏。
  • Invert Vector2/Vector3(反转):反转某个轴,常用于满足玩家“反转Y轴”的偏好设置。
  • Normalize Vector2/3(标准化):确保斜向输入向量的长度不超过1。对于键盘WASD输入,使用“2D Vector Composite”时,它内部已经处理了标准化,使得同时按WA键移动时,速度不会超过按单个W键。

实操心得:为手柄摇杆添加一个适度的死区(如0.125-0.2)是标准操作,能极大提升操作手感。处理器的配置是按Binding设置的,这意味着你可以为键盘移动和手柄移动设置不同的死区或缩放值,非常灵活。

5.3 动态切换Action Map(游戏状态切换)

游戏通常有不同的状态,比如“探索中”、“对话中”、“菜单打开”。我们需要在不同状态下启用不同的输入集。

public class GameInputManager : MonoBehaviour { private PlayerControls _controls; private void Awake() { _controls = new PlayerControls(); // 默认只启用UI,比如开始菜单 SwitchToUIMap(); } public void SwitchToPlayerMap() { _controls.UI.Disable(); _controls.Player.Enable(); Debug.Log("切换到玩家控制"); } public void SwitchToUIMap() { _controls.Player.Disable(); _controls.UI.Enable(); Debug.Log("切换到UI控制"); } // 例如,当打开暂停菜单时调用 public void OnPauseMenuOpened() { SwitchToUIMap(); // 同时可能需要暂停游戏时间 Time.timeScale = 0f; } public void OnPauseMenuClosed() { SwitchToPlayerMap(); Time.timeScale = 1f; } }

关键就是Disable()旧的Map,然后Enable()新的Map。确保同一时间只有一个处理游戏核心逻辑的Map被激活。

6. 实战:构建一个完整的可操作角色

让我们把前面学的整合起来,创建一个支持移动、奔跑、跳跃、攻击的第三人称角色。

6.1 扩展Input Action Asset

在之前的“PlayerControls”中,我们增加几个Action:

  • Sprint(Button类型):绑定到<Keyboard>/leftShift<Gamepad>/leftStickPress(左摇杆按下)。
  • Attack(Button类型):绑定到<Mouse>/rightButton<Gamepad>/rightShoulder(RB键)。
  • Aim(Button类型):绑定到<Mouse>/rightButton(长按进入瞄准模式)?这里我们先简单处理。

6.2 编写增强版角色控制器

using UnityEngine; using UnityEngine.InputSystem; [RequireComponent(typeof(CharacterController))] public class AdvancedPlayerController : MonoBehaviour { [Header("Movement")] public float walkSpeed = 3f; public float sprintSpeed = 6f; public float rotationSpeed = 10f; public float jumpHeight = 1.5f; public float gravity = -9.81f; [Header("Ground Check")] public Transform groundCheck; public float groundDistance = 0.4f; public LayerMask groundMask; private CharacterController _controller; private PlayerControls _controls; private Transform _cameraTransform; private Vector2 _moveInput; private Vector3 _velocity; private bool _isGrounded; private bool _isSprinting = false; private float _currentSpeed; private void Awake() { _controller = GetComponent<CharacterController>(); _controls = new PlayerControls(); _cameraTransform = Camera.main.transform; // 假设主相机是第三人称相机 _currentSpeed = walkSpeed; } private void OnEnable() => _controls.Player.Enable(); private void OnDisable() => _controls.Player.Disable(); private void Start() { // 订阅事件 _controls.Player.Move.performed += ctx => _moveInput = ctx.ReadValue<Vector2>(); _controls.Player.Move.canceled += ctx => _moveInput = Vector2.zero; _controls.Player.Sprint.performed += ctx => _isSprinting = true; _controls.Player.Sprint.canceled += ctx => _isSprinting = false; _controls.Player.Jump.performed += OnJump; _controls.Player.Attack.performed += OnAttack; } private void Update() { // 1. 地面检测 _isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask); if (_isGrounded && _velocity.y < 0) { _velocity.y = -2f; // 一个小的向下力,确保角色紧贴地面 } // 2. 处理冲刺 _currentSpeed = _isSprinting ? sprintSpeed : walkSpeed; // 3. 移动(基于相机方向) Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y); move = _cameraTransform.forward * move.z + _cameraTransform.right * move.x; move.y = 0; // 确保移动在水平面 if (move.magnitude > 0.1f) { // 平滑旋转角色朝向移动方向 Quaternion targetRotation = Quaternion.LookRotation(move.normalized); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); // 应用移动 _controller.Move(move.normalized * _currentSpeed * Time.deltaTime); } // 4. 重力与跳跃 _velocity.y += gravity * Time.deltaTime; _controller.Move(_velocity * Time.deltaTime); } private void OnJump(InputAction.CallbackContext context) { if (_isGrounded) { // 简单的跳跃物理:v = sqrt(2 * g * h) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); } } private void OnAttack(InputAction.CallbackContext context) { Debug.Log("Attack!"); // 在这里触发攻击动画、生成攻击碰撞盒、计算伤害等 // 例如:PlayAnimation("Attack"), StartCoroutine(EnableHitbox())... } }

这个控制器实现了基于相机方向的移动、冲刺切换、重力跳跃和攻击触发。它展示了如何将Input System的输入事件与传统的Unity游戏逻辑(CharacterController, Physics)紧密结合。

7. 常见问题排查与解决方案实录

即使按照教程一步步来,你也可能会遇到一些坑。以下是我在项目和社区中总结的最高频问题。

7.1 安装后编辑器无响应或脚本编译错误

  • 现象:安装Input System后,编辑器卡住,或者脚本大量报错(找不到UnityEngine.Input命名空间)。
  • 原因:项目中原有脚本大量引用了旧的UnityEngine.InputAPI。切换输入系统后,旧的API在“Input System Package (New)”模式下不可用。
  • 解决
    1. 临时方案:前往Edit -> Project Settings -> Player,在“Other Settings”里将“Active Input Handling”改回“Both”。这样新旧系统可以共存,方便逐步迁移。但这不是长久之计,因为“Both”模式可能有性能开销和潜在冲突。
    2. 根本方案:按照本教程,逐步将旧代码迁移到新的Input System API。可以使用全局搜索替换,但更推荐重写输入逻辑部分。

7.2 Input Action Asset在脚本中引用为null

  • 现象:在脚本中public PlayerInput playerInput;并拖拽赋值,或者通过Asset.Find查找,运行时发现它为null。
  • 原因:Input Action Asset是一个可序列化的资源文件(.inputactions),它需要被加载到内存中。如果通过new关键字实例化自动生成的包装类(如new PlayerControls()),这个实例与Project视图中的Asset文件是两个独立的对象。直接拖拽到Inspector的,是Asset文件本身的一个引用,而new出来的是运行时的一个副本。
  • 解决
    • 方案A(推荐,使用自动生成类):就像我们教程中做的,直接实例化包装类(new PlayerControls())。这个实例会在运行时自动加载关联的Asset数据。确保在脚本的OnEnable中调用Enable(),在OnDisable中调用Disable()
    • 方案B(使用PlayerInput组件):给GameObject添加PlayerInput组件,在“Actions”字段拖入你的.inputactions文件。然后在脚本中通过GetComponent<PlayerInput>().actions来获取InputActionAsset,或者通过消息发送、UnityEvent等方式接收输入。这种方式更可视化,适合快速原型。

7.3 输入无响应(特别是UI输入)

  • 现象:游戏内角色控制正常,但UI按钮点击、导航无效。
  • 原因:UI系统(如Unity的EventSystem)默认使用旧的Standalone Input Module,它不认识新的Input System。
  • 解决
    1. 选中场景中的EventSystem游戏对象(如果没有,Canvas被创建时会自动生成一个)。
    2. 在Inspector中,你会看到“Standalone Input Module”组件。删除它
    3. 点击“Add Component”,搜索并添加“Input System UI Input Module”。
    4. 确保你的UI Action Map(例如名为“UI”)已经被启用,并且其中的“Point”(鼠标/触摸位置)、“Click”(点击)、“Navigate”(方向键导航)等Action已经正确绑定。

7.4 打包后(尤其是WebGL)输入失效

  • 现象:在编辑器中运行完美,但打包成PC、Android或WebGL后,输入没反应。
  • 原因1(通用):Input Action Asset没有被包含在构建中。
    • 解决:确保.inputactions文件在Resources文件夹下,或者其所在的文件夹/文件被显式地包含在构建场景的依赖中。最保险的方法是将其放在Resources文件夹,或者通过脚本在AwakeStart中通过Resources.Load加载。
  • 原因2(WebGL特有):WebGL平台需要处理浏览器焦点的丢失。
    • 解决:在Player Settings (Edit -> Project Settings -> Player) 中,找到WebGL发布设置,在“Resolution and Presentation”下,确保“Disable Context Menu”是勾选的,或者添加处理浏览器焦点事件的代码。更关键的是,Input System的WebGL后端可能对某些输入处理不同,确保在WebGL下测试基础的键盘鼠标输入。

7.5 手柄连接/断开时的问题

  • 现象:游戏支持手柄,但玩家在游戏过程中拔掉手柄再插上,或者切换不同手柄后,输入失灵。
  • 原因:设备通知没有正确处理。
  • 解决:Input System提供了Input.onDeviceChange事件。你可以监听这个事件,在设备添加或移除时,重新绑定或刷新你的输入状态。
private void OnEnable() { InputSystem.onDeviceChange += OnDeviceChanged; } private void OnDisable() { InputSystem.onDeviceChange -= OnDeviceChanged; } private void OnDeviceChanged(InputDevice device, InputDeviceChange change) { switch (change) { case InputDeviceChange.Added: Debug.Log($"设备已连接: {device.name}"); // 可以在这里为新增的设备动态添加绑定,或刷新UI提示 break; case InputDeviceChange.Removed: Debug.Log($"设备已断开: {device.name}"); // 如果当前玩家正在使用这个设备,可以切换到键盘鼠标控制,或暂停游戏 break; case InputDeviceChange.ConfigurationChanged: // 设备配置改变(如手柄从XInput模式切换到DInput模式) break; } }

7.6 输入延迟或感觉“不跟手”

  • 现象:按键响应有延迟,尤其是快速连击时。
  • 原因
    1. 事件处理位置不当:在FixedUpdate中处理瞬时输入(如跳跃),而FixedUpdate的频率(默认50Hz)低于Update,可能导致一帧的延迟。
    2. 处理器链过长或过于复杂:在Input Action Asset中为绑定添加了过多自定义处理器(Processors)或交互器(Interactions),每一帧都需要计算。
    3. 脚本执行顺序:处理输入的脚本可能在Update循环中执行顺序靠后。
  • 解决
    1. 对于按钮瞬时动作,永远在Update循环相关的函数(Update本身或事件回调)中处理,而不是FixedUpdateFixedUpdate只适合处理基于物理的连续力。
    2. 简化交互逻辑。除非必要,避免嵌套使用多个复杂交互器。
    3. Edit -> Project Settings -> Script Execution Order中,将核心输入处理脚本的优先级调高(设置更小的数字),确保它在其他依赖输入的脚本之前执行。

8. 性能优化与最佳实践建议

当项目规模变大,输入逻辑复杂时,以下几点能帮你保持代码高效和可维护。

  1. 使用单例或服务类管理输入:不要在每个需要输入的脚本中都实例化一个PlayerControls。创建一个全局的InputManager单例或通过依赖注入提供服务。所有其他脚本通过这个管理器来访问输入状态或订阅事件。这避免了重复的初始化和潜在的状态不一致。
  2. 区分输入与逻辑:输入层只负责收集和转发输入事件。业务逻辑层(如角色状态机、武器系统)监听这些事件,并决定如何响应。不要在处理输入的代码里直接播放动画或修改大量游戏状态。
  3. 善用“Pass Through” Action Type:对于像“暂停菜单”这种需要全局捕获的按钮(如ESC键),可以将其Action Type设为“Pass Through”。这意味着该输入不会被任何Action Map“消耗”,无论当前哪个Map激活,它都能被触发。
  4. 为移动平台优化:在移动设备上,考虑使用TouchscreenOn-Screen Controls。Input System支持创建虚拟摇杆和按钮。利用InputSystemUIInputModule来处理触摸UI,并为虚拟控制器创建独立的Action Map。
  5. 存档与重置输入设置:如果你实现了自定义按键绑定(Rebinding),记得将用户修改后的绑定路径(binding.overridePath)用JsonUtility.ToJson序列化后保存到PlayerPrefs或文件中。加载时再用JsonUtility.FromJson反序列化并应用(binding.ApplyBindingOverride)。