首页 / 资讯中心 / 文章详情

Unity新输入系统实战指南:Action抽象与跨平台配置

Unity新输入系统实战指南:Action抽象与跨平台配置 ★ FEATURED ARTICLE
说实话Unity 从 2019 年开始把新输入系统Input System包塞进 Package Manager 的时候我是持观望态度的。那会儿项目组里人多嘴杂老的Input.GetAxis用得顺手改接口、改配置、改设备兼容逻辑动一个输入模块就像动地基。后来被逼着迁移了两次一次是公司自研硬件需要自定义设备一次是给 PICO 做手柄交互这才真正把新输入系统吃透。结论放前面如果你的项目要上多平台、要接手柄/触屏/键鼠切换、要支持玩家自定义按键那新输入系统是绕不开的正道如果只是做个 2D 小游戏老 Input Manager 也不是不能用但 Unity 官方已经在推动新系统落地新项目的坑越踩越平早迁移早省心。这篇内容我从“为什么换”讲到“怎么配”再到“代码怎么写、坑怎么排”尽量按实战顺序来。适合正在学 Unity 的新手也适合那些在旧项目上被输入模块折磨过的老哥们。1. 输入这件事为什么要推倒重来1.1 老 Input Manager 的四个硬伤先说清楚旧系统到底哪里让人难受不然你很难理解新系统那些设计背后的用意。老Input类本质是一个全局静态接口你想读轴、读按键直接Input.GetKeyDown(KeyCode.Space)就行。看着方便但项目一复杂就露馅按键映射写死在代码里玩家想改键位你得上第三方插件或者自己写一套 UI 配置映射手柄、键盘、触屏的检测逻辑各自为战跨平台时经常要做一堆条件分支Input.GetAxis(Horizontal)这种字符串魔法值容易拼错编译期不报错运行期才发现旧系统对回报率、多触点、高阶设备比如 VR 手柄、触摸板、陀螺仪支持很弱很多数据拿不到。我在一个老项目里就吃过亏做 PC 安卓双端安卓上要检测手势滑动PC 上要支持手柄右摇杆旋转视角。老 Input 那边Touch的封装还能凑合用手柄摇杆得自己去映射轴名不同品牌手柄轴顺序还不一样最后只能靠写一堆#if UNITY_ANDROID做平台分支代码丑得没法看还经常出一个平台改崩另一个平台。1.2 新输入系统的核心思路动作Action代替按键KeyCode新输入系统最核心的转变是引入了一层“动作”的抽象。你不是去读“空格键是否按下”而是定义“Jump 这个动作是否触发”再在配置文件里把“空格键”或“手柄 A 键”绑定到 Jump 上。这样代码里永远只关心“Jump”具体是哪个设备哪个键交给配置去管。这个抽象带来的好处非常直观。玩家改了键位配置文件跟着变代码一行都不用动你要让同一个游戏支持 PC 键鼠、手柄、手机触屏只要在 Action 上挂多组绑定运行时会自动根据“当前哪个设备在操作”来切换输入来源。这种体验在老 Input Manager 下想都不敢想。2. 环境准备与基础配置首次接入必看2.1 安装 Input System 包打开 Package ManagerWindow - Package Manager左上角包来源选“Unity Registry”搜索“Input System”。需要注意包名后面不会标“Preview”但功能上它早就成熟了我最早在 2019.4 上用过 1.0 正式版现在在 Unity 2022 和 Unity 6 上跑的也是同一条 API 主线不用太担心兼容性。装完之后 Unity 会弹一个对话框问你“启用新输入系统需要重启编辑器”其实它是在改 Player Settings 里的 Active Input Handling。我建议先别急着重启把下面几项设置看完再一次性重启不然你会反复被编辑器重启打断。2.2 Active Input Handling 到底选哪个这个选项在 Project Settings - Player - Other Settings - Active Input Handling有三个值Input Manager (Old)、Input System Package (New)、Both。它是全项目级别的开关影响所有脚本和框架的输入入口。选 New彻底关闭旧系统UnityEngine.Input类大部分接口不可用会报编译错误选 Both新旧并存Editor 会多编译一层兼容代码性能有轻微损耗但迁移期最稳选 Old等于没启用新系统装包也没用。我在正式项目里一般直接选 New。既然决定迁移就别拖泥带水启用 Both 之后团队里总会有人图省事继续写Input.GetKeyDown到最后新系统只配了个空壳改键功能永远没法落地。纯学习或者老项目渐进式迁移时才建议临时用 Both。2.3 别忘了 EventSystem 的输入模块如果你项目里有 UI装完新输入系统后会发现一个容易踩的现象UI 上的按钮点击没反应。原因是老版 EventSystem 默认带的是 Standalone Input Module它只认旧输入系统。新输入系统包装好后会自动给 EventSystem 添加 InputSystemUIInputModule但如果你是在旧项目里手动迁移很可能把这个模块漏了。检查方式是选中场景里的 EventSystem看 Inspector 里挂的是哪个模块。如果只有 Standalone Input Module建议右键删掉再点 Add Component 搜索 InputSystemUIInputModule 添加。不加这个后面你的 UI 界面点不了游戏物体上的输入却正常排查半天容易怀疑人生。3. 核心概念拆解Action、Action Map、Binding 与设备抽象3.1 一张图搞懂四个名词的关系我习惯把这几个概念用“遥控器”来类比。Action Map 就是遥控器上的模式切换电视模式、空调模式每个模式下一组 Action 按键Action 就是遥控器上的一个具体功能键音量、电源Binding 是“这个键在特定设备上对应哪个物理按键”Control 则是设备上真实存在的输入通道比如键盘的 W 键、鼠标的 X 轴增量、手柄的左摇杆 Y 轴。放在 Unity 的新输入系统里要创建的就是一份 Input Action Asset。Asset 里可以建多个 Action Map每个 Map 下建多个 Action每个 Action 下面再挂多个 Binding。运行时可以整体启用某个 Map比如进入 UI 界面就启用 UI Map禁用 Gameplay Map非常干净。3.2 同一个动作绑定多组设备打开 Input Action Asset 后给 Action 添加 Binding 时左上角的“”下拉菜单会让你选 Binding TypeButton、Value、Pass-Through 等还会列出常见设备模板。比如 Action 是“Move”二维方向我一般给它加两组绑定一组是 WASD 的四键组合W 对应 up composite、S 对应 down、A 对应 left、D 对应 right另一组是“左摇杆Vector2”。这样同一套逻辑键鼠用户按 WASD手柄用户推摇杆都能得到Vector2输入值。这里有个很容易踩的细节当你选择“Add Binding”里的“2D Vector Composite”时Unity 会自动创建四个子绑定加一个组合父级看起来结构有点绕但千万别手动把它们拆散。拆了之后合成值会丢失移动方向要么乱跳要么直接不动。3.3 用生成代码接管繁琐配置Action Asset 配置好后很多教程是让你在 Inspector 里把某个 Action 拖到 MonoBehaviour 的 public 字段上或者在组件里引整个 Asset。这么做能用但字符串查找和拖引用都很松散没法做静态类型检查。我强烈建议打开 Asset 的 Inspector勾选底部“Generate C# Class”设置好类名默认是 Asset 名加“Controls”点 Apply。Unity 会自动生成一个强类型类把 Action、Map、Bindings 全部包装成属性和事件。生成之后代码里可以直接using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { private PlayerControls controls; private void Awake() { controls new PlayerControls(); } private void OnEnable() { controls.Enable(); controls.Gameplay.Jump.performed OnJump; controls.Gameplay.Move.performed ctx moveInput ctx.ReadValueVector2(); controls.Gameplay.Move.canceled ctx moveInput Vector2.zero; } private void OnDisable() { controls.Gameplay.Jump.performed - OnJump; controls.Disable(); } private void OnJump(InputAction.CallbackContext context) { // 跳跃逻辑 } }这样写编译器会在你把 Action 名拼错的第一时间报错比运行期查日志强一百倍。4. 实操从零配置一个第一人称角色的输入4.1 创建 Input Action Asset 并配置移动、跳跃、射击在 Project 窗口右键 - Create - Input Actions命名PlayerControls.inputactions双击打开编辑器。我建议建三个 Action Map分别叫 Gameplay、UI、Dialogue目前先配 Gameplay。Gameplay 下面建这些 ActionMove类型 Value控制类型 Vector2Look类型 Value控制类型 Vector2Jump类型 ButtonFire类型 Button。Move 的 Binding 按 3.2 的方式加“WASD 合成”和“左摇杆”。Look 的 Binding 加“鼠标 DeltaVector2”和“右摇杆Vector2”。Jump 绑定 Space 和手柄 A 键Gamepad / buttonSouth。Fire 绑定鼠标左键Mouse / leftButton和手柄 RT 键Gamepad / rightTrigger。这里解释一下为什么 Jump 和 Fire 的类型用 Button 而不是 Value。Button 适合“按下瞬间触发”的离散动作Value 适合“持续取值”的轴类动作。如果你把 Move 配成 Button你拿到的就是一个 1 和 0 的开关而不是方向向量移动代码会变得很别扭。类型选错是新手最常见的配置错误之一没有语法报错但行为完全不符合预期。4.2 用 PlayerInput 组件快速接入一种非常省事的接入方式是给玩家 GameObject 挂 PlayerInput 组件再把 Action Asset 拖到 Actions 字段。PlayerInput 会自动管理 Action Map 的启用、设备切换和默认行为。比如你想用“键鼠时显示准星、手柄时显示另一套 UI”可以在 PlayerInput 的 Messages 选项里选择“Invoke Unity Events”或“Invoke CSharp Events”然后通过回调通知。不过要提醒一点PlayerInput 适合快速原型和中轻度项目它帮你藏的细节一多出问题时你反而不好定位。所以我更推荐“PlayerInput 管设备 生成代码管逻辑”的混用模式或者干脆只用生成代码。别把所有交互都寄托在 PlayerInput 的 Inspector 配置上配置越隐式后期越难维护。4.3 纯代码方式读取输入适合不爱拖组件的同学如果你不想挂 PlayerInput直接操作生成类也一样干净。核心就三件事Enable 启用、订阅事件或轮询值、Disable 释放。移动这种连续动作适合在 Update 里轮询private void Update() { Vector2 move controls.Gameplay.Move.ReadValueVector2(); Vector2 look controls.Gameplay.Look.ReadValueVector2(); transform.Translate(new Vector3(move.x, 0, move.y) * moveSpeed * Time.deltaTime); }跳跃和射击适合订阅事件controls.Gameplay.Jump.performed OnJump; controls.Gameplay.Fire.performed OnFire;这里有个很多人忽略的细节的事件会在“按下”时触发一次但如果你按住不放它不会再次触发。想要按住自动连发需要监听,而 Unity 的 effected你需要监听performed之后自己维护一个按住状态或者把 Fire 的 Action 改成 Value 类型用ctx.ReadValuefloat() 0.5f判断按住。具体用哪种取决于你的射击功能是单发还是自动连发。4.4 让 UI 按钮也能触发游戏动作新输入系统的好处之一是你可以在 UI 界面里直接拖动控制。做法是给 UI 按钮绑定一个InputActionUI类似的事件或者在按钮的 OnClick 事件里直接调用controls.Gameplay.Jump.Enable()然后在事件里执行跳跃逻辑。其实更常见的场景是在暂停菜单里切换 Action Map。进入暂停时禁用 Gameplay Map、启用 UI Map这样玩家点在 UI 上不会误触发移动和射击。我自己做 RPG 的时候就是靠这招把对话、战斗、菜单三种模式切得干干净净。public void EnterPauseMenu() { controls.Gameplay.Disable(); controls.UI.Enable(); } public void ExitPauseMenu() { controls.UI.Disable(); controls.Gameplay.Enable(); }5. 常见问题与排查技巧实录5.1 按了按键没反应这个现象排第一因为它概率最高。我自己排查时的固定顺序是先看 Inspector 里 Action Asset 有没有启用Map 前面有没有勾选再看 C# 代码里有没有Enable()最后看是不是被另一个 Action Map 或者 Binding 的互斥条件干扰了。如果是用了生成类确认controls.Gameplay.Enable()在OnEnable里调用过而不是放在Awake里就再也不管了。还有一个小细节如果你在代码里禁用了某个 Action比如controls.Gameplay.Move.Disable()那即使 Map 是启用的Move 也不会生效。新输入系统里 Action 和 Action Map 是两级开关任何一级都可能是堵住输入的那道闸。5.2 旧代码 Input.GetKeyDown 还能用吗如果 Active Input Handling 选了 New旧 Input 接口基本不可用。如果选了 Both那旧接口还能跑但新系统环境里偶尔会有奇怪的双重触发问题。比如你既用新系统处理了Fire动作又在别的脚本里写了Input.GetMouseButtonDown(0)那一次点击可能触发两次逻辑。迁移的时候我建议全局搜索Input.逐个替换成新系统的事件或值读取。替换并不难对照表大致是这样的旧写法新写法Input.GetKeyDown(KeyCode.Space)订阅 Jump 的performed事件Input.GetAxis(Horizontal)Move.ReadValueVector2().xInput.GetMouseButton(0)Fire 的ReadValuefloat()判断Input.mousePosition用 Pointer 的 position Action 读取再转屏幕坐标但要提醒下Input.mousePosition换成新系统后不是直接读一个属性而要从一个类型为 Value、控制类型为 Vector2 的 Action 里拿或者通过Mouse.current.position.ReadValue()。很多老教程直接搜不到新写法容易卡在这。5.3 UI 点击失灵与事件系统配置这个问题在 2.3 已经提过这里补充一个更隐蔽的情况当你的 Active Input Handling 是 Both并且场景里同时挂了 Standalone Input Module 和 InputSystemUIInputModuleUI 事件可能会被处理两次表现为按钮响应一次后又立刻取消。解决办法是只保留 InputSystemUIInputModule。另外如果你用EventSystem.current.IsPointerOverGameObject()判断是否点在 UI 上新系统下这个接口偶尔会失效更稳的写法是用InputSystemUIInputModule提供的射线结果或者直接用EventSystem.current.IsPointerOverGameObject(Pointer.current.deviceId)传入设备 ID。这个坑我调了一下午最后看官方论坛才明白。5.4 移动端多点触控与手柄兼容注意事项手机上用新输入系统建议不要只配“TouchscreenContact 单独位置”这样的零散绑定而是用系统内置的“Touchscreen / PrimaryPosition”和“Touchscreen / PrimaryPressure”这类组合绑定再把控制类型设为 Vector2 或 Axis。Unity 会自动把第一根手指作为主触点后续手指进到 secondary 绑定里。多点触控要自己做“双指缩放”的话需要监听 Touchscreen.current.touches 数组不能只靠 Action 默认绑定。手柄兼容方面新系统最大的优势是它对平台差异封装得比较好。微软系、索尼系手柄按键映射基本统一Gamepad / buttonSouth在不同平台上都指代“确认键”不用自己写平台判断。实测下来PS 手柄和 XBOX 手柄在 Windows、安卓上都稳定唯一要留意的是不同手柄的摇杆死区值不一样建议在代码里给 Look 或 Move 的 Action 加上Sensitivity和Deadzoneprocessors。这个在 Action 的 Inspector 面板里可以直接加不需要写代码。5.5 Action Asset 引用丢失与生成代码未更新如果你把.inputactions文件从一个项目拷贝到另一个项目或者重命名了文件Inspector 上可能会显示 Missing Reference。这个没有太多技术含量重新拖一次引用就好。更容易踩的是你改了PlayerControls.inputactions里的配置但生成类没重新生成操作后代码里读到的还是旧配置。改完 Asset 别忘回到生成类设置界面再点一次 Apply。还有一个比较烦人的生成类的文件名和 Action Asset 同名如果你修改类名大量引用你的脚本里using和变量类型全都得跟着换。建议项目一开始就定好类名别中途改。6. 我的几条实战心得与建议先说设备切换这事。我最初做多平台项目时总想自己写一套“识别当前输入设备”的逻辑后来发现新系统已经内置了InputDevice.activeDeviceChanged和PlayerInput的 device 切换事件。你只需要订阅它在回调里刷新 UI 提示就行。比如玩家用手柄操作后屏幕上的按钮提示自动从“按空格”换成“按 A”这个体验做出来非常加分。再说说性能。很多人担心新系统在回调里跑逻辑会不会拖慢游戏实测下来完全不需要担心。Input System 底层已经做了良好的批处理和状态缓存事件回调的开销远小于在 Update 里每帧轮询判断一堆字符串。反而我要提醒的是别滥用Pass-Through类型的 Action它会把所有设备事件都透传给你频率极高适合做模拟器不适合做常规输入。最后提一个适合扩展的方向如果你要做玩家自定义按键新系统自带InputActionRebindingExtensions几行代码就能把某个 Binding 重新绑定到玩家按下的任意键并且还能保存成 JSON、读回来恢复。var rebindOperation controls.Gameplay.Jump.PerformInteractiveRebinding() .WithTargetBinding(bindingIndex) .Start(); rebindOperation.OnComplete(op { op.Dispose(); var json controls.SaveBindingOverridesAsJson(); PlayerPrefs.SetString(customBindings, json); });这个功能是真的能直接搬进正式项目的。我自己在动作游戏里就是用它做重按键配合简单的 UI 弹窗提示效果比很多商业插件还干净。新输入系统虽然上手要多记几个概念但它把扩展性和跨平台问题一次性解决了这个投入值得。最后再分享一个调试小技巧运行时想快速确认某个 Action 有没有触发可以在 Action Asset 编辑器的运行时 debug 面板里看数值变化也可以临时加一句Debug.Log(ctx.action.name ctx.phase)。新系统里 context 的 phase 有Started、Performed、Canceled三种搞清楚这三个阶段你的输入逻辑基本不会再玄学失灵。
阅读完成 · 觉得有帮助?
咨询建站