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
安装过程本身非常简单,但有几个细节点需要留意,这些细节往往决定了后续配置是否顺利。
- 打开Package Manager:在Unity编辑器顶部菜单栏,依次点击
Window->Package Manager。 - 切换资源库:在Package Manager窗口的左上角,你会看到一个下拉菜单,默认可能是“Packages: Unity Registry”或“Packages: My Assets”。请确保它被设置为“Unity Registry”。这是从Unity官方包服务器获取包的必要步骤。
- 搜索与安装:在搜索栏(通常位于窗口右上角)中输入“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
理论说再多不如动手操作。让我们来创建一个:
- 在Project窗口右键,选择
Create->Input Actions。我给文件命名为“PlayerControls”。 - 双击这个新创建的
.inputactions文件,会打开一个专门的Input Actions编辑器窗口。
这个窗口就是你的主战场。默认会有一个名为“New action map”的Action Map,将其重命名为“Player”。然后,我们开始添加Action。
3.2 定义基础Action:移动、跳跃与攻击
在“Player”这个Action Map下,点击“+”号添加Action。
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移动的标准配置。
- Action Type:选择
Jump(按钮类型):
- Action Type:选择
Button。这表示一个瞬时的按下/松开动作。 - 添加绑定:
<Keyboard>/space和<Gamepad>/buttonSouth(通常是A键或X键,取决于手柄布局)。
- Action Type:选择
Fire(按钮类型):
- Action Type:
Button。 - 添加绑定:
<Mouse>/leftButton和<Gamepad>/rightTrigger(右扳机键)。
- Action Type:
注意事项:在绑定键盘按键时,系统可能会显示如“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(摇杆死区):摇杆在中心位置常有微小漂移,死区可以忽略这个范围内的输入。
Min和Max定义了死区的形状和范围,一般用默认的“轴向(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)”模式下不可用。 - 解决:
- 临时方案:前往
Edit -> Project Settings -> Player,在“Other Settings”里将“Active Input Handling”改回“Both”。这样新旧系统可以共存,方便逐步迁移。但这不是长久之计,因为“Both”模式可能有性能开销和潜在冲突。 - 根本方案:按照本教程,逐步将旧代码迁移到新的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等方式接收输入。这种方式更可视化,适合快速原型。
- 方案A(推荐,使用自动生成类):就像我们教程中做的,直接实例化包装类(
7.3 输入无响应(特别是UI输入)
- 现象:游戏内角色控制正常,但UI按钮点击、导航无效。
- 原因:UI系统(如Unity的EventSystem)默认使用旧的
Standalone Input Module,它不认识新的Input System。 - 解决:
- 选中场景中的
EventSystem游戏对象(如果没有,Canvas被创建时会自动生成一个)。 - 在Inspector中,你会看到“Standalone Input Module”组件。删除它。
- 点击“Add Component”,搜索并添加“Input System UI Input Module”。
- 确保你的UI Action Map(例如名为“UI”)已经被启用,并且其中的“Point”(鼠标/触摸位置)、“Click”(点击)、“Navigate”(方向键导航)等Action已经正确绑定。
- 选中场景中的
7.4 打包后(尤其是WebGL)输入失效
- 现象:在编辑器中运行完美,但打包成PC、Android或WebGL后,输入没反应。
- 原因1(通用):Input Action Asset没有被包含在构建中。
- 解决:确保.inputactions文件在
Resources文件夹下,或者其所在的文件夹/文件被显式地包含在构建场景的依赖中。最保险的方法是将其放在Resources文件夹,或者通过脚本在Awake或Start中通过Resources.Load加载。
- 解决:确保.inputactions文件在
- 原因2(WebGL特有):WebGL平台需要处理浏览器焦点的丢失。
- 解决:在Player Settings (
Edit -> Project Settings -> Player) 中,找到WebGL发布设置,在“Resolution and Presentation”下,确保“Disable Context Menu”是勾选的,或者添加处理浏览器焦点事件的代码。更关键的是,Input System的WebGL后端可能对某些输入处理不同,确保在WebGL下测试基础的键盘鼠标输入。
- 解决:在Player Settings (
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 输入延迟或感觉“不跟手”
- 现象:按键响应有延迟,尤其是快速连击时。
- 原因:
- 事件处理位置不当:在
FixedUpdate中处理瞬时输入(如跳跃),而FixedUpdate的频率(默认50Hz)低于Update,可能导致一帧的延迟。 - 处理器链过长或过于复杂:在Input Action Asset中为绑定添加了过多自定义处理器(Processors)或交互器(Interactions),每一帧都需要计算。
- 脚本执行顺序:处理输入的脚本可能在
Update循环中执行顺序靠后。
- 事件处理位置不当:在
- 解决:
- 对于按钮瞬时动作,永远在
Update循环相关的函数(Update本身或事件回调)中处理,而不是FixedUpdate。FixedUpdate只适合处理基于物理的连续力。 - 简化交互逻辑。除非必要,避免嵌套使用多个复杂交互器。
- 在
Edit -> Project Settings -> Script Execution Order中,将核心输入处理脚本的优先级调高(设置更小的数字),确保它在其他依赖输入的脚本之前执行。
- 对于按钮瞬时动作,永远在
8. 性能优化与最佳实践建议
当项目规模变大,输入逻辑复杂时,以下几点能帮你保持代码高效和可维护。
- 使用单例或服务类管理输入:不要在每个需要输入的脚本中都实例化一个
PlayerControls。创建一个全局的InputManager单例或通过依赖注入提供服务。所有其他脚本通过这个管理器来访问输入状态或订阅事件。这避免了重复的初始化和潜在的状态不一致。 - 区分输入与逻辑:输入层只负责收集和转发输入事件。业务逻辑层(如角色状态机、武器系统)监听这些事件,并决定如何响应。不要在处理输入的代码里直接播放动画或修改大量游戏状态。
- 善用“Pass Through” Action Type:对于像“暂停菜单”这种需要全局捕获的按钮(如ESC键),可以将其Action Type设为“Pass Through”。这意味着该输入不会被任何Action Map“消耗”,无论当前哪个Map激活,它都能被触发。
- 为移动平台优化:在移动设备上,考虑使用
Touchscreen和On-Screen Controls。Input System支持创建虚拟摇杆和按钮。利用InputSystemUIInputModule来处理触摸UI,并为虚拟控制器创建独立的Action Map。 - 存档与重置输入设置:如果你实现了自定义按键绑定(Rebinding),记得将用户修改后的绑定路径(
binding.overridePath)用JsonUtility.ToJson序列化后保存到PlayerPrefs或文件中。加载时再用JsonUtility.FromJson反序列化并应用(binding.ApplyBindingOverride)。
