资讯动态

Unity新输入系统Input System实战:从安装到首个输入动作与避坑指南

发布时间:2026/10/14 7:28:20 来源:尧图企业网站定制
我刚用Unity做项目的时候用的还是老一套的Input Manager。当时项目里要同时支持键盘、鼠标、手柄还得处理移动端触控结果代码写了一堆Input.GetAxis(Horizontal)配合Input.GetKeyDown(KeyCode.Space)然后在每个需要输入的脚本里各写各的判断逻辑。后来升级到新版 Input System才意识到以前那种写法在新项目里越来越难维护。这篇东西不是官方文档的翻译是我在几个实际项目里切换输入系统后整理的经验重点讲清楚 Input System 到底是什么、为什么值得换、从安装到第一个能跑起来的输入动作应该怎么做以及那些文档里不会写但在工程里一定会踩的坑。如果你正准备从老系统迁移或者刚接触 Unity 想直接学新系统这一篇可以当作起点后续再聊复杂组合键、多设备切换和回调架构这些进阶内容。1. 认识Input System为什么Unity要做这个翻新1.1 老输入系统到底卡在哪老输入系统Input Manager这些年帮Unity撑过了无数项目但它的问题在项目规模变大以后非常明显。首先是输入设备的支持太依赖硬编码比如PC上你习惯了Input.GetKey(KeyCode.W)这种写法一旦要求支持手柄、触摸或者自定义按键重映射就得靠自己写一大堆中间层。Unity官方后来推出的旧版输入系统虽然也能读手柄轴和按钮但代码写得好的团队基本都会在外面再包一层输入抽象导致不同项目的输入层实现五花八门新成员进来还得先学一遍项目自己的输入框架。另一个麻烦是输入事件只能在主循环里轮询读取。每一帧调用Input.GetAxis时你拿到的是一帧内叠加后的状态有时候你想知道轴上这个变化量是从哪台设备来的或者“这一帧有没有按下”并不直观。在某些对响应要求很高的玩法里比如格斗游戏检测精确的“按下”时序用轮询写起来非常痛苦。触摸屏和鼠标输入在老系统里也经常被吐槽Windows上鼠标点击在部分平台配合多显示器会出现坐标偏移这类问题排查成本很高。还有输入设备和操作意图的耦合问题。老系统里所有按键都是全局唯一键值你很难统一声明“我在这里需要的是一个跳跃动作它可能来自空格键、也可能来自手柄B键”。当策划突然说“跳跃别用空格了改成E键”你就得全局搜索替换很容易漏。项目进入多语言版本或多人对战需求时按键冲突和自定义设置就更无从谈起了。1.2 新系统带来的核心变化新版 Input System 从底层改变了游戏读取输入的思路。它默认帮你在“物理设备”和“动作意图”之间加了一层抽象。比如你可以定义一个名为Jump的 Action它可以同时绑定到键盘上的空格键也可以绑定到手柄的南键通常就是A键或B键玩家用哪个设备触发了跳跃系统会在运行时自动匹配。开发者在脚本里只关心“我收到一个跳跃事件”而不需要关心这个事件来自哪里。这套系统基于事件驱动不再是每帧读状态而是通过started、performed、canceled这些回调把输入变化告诉你。写响应式玩法的时候逻辑反而更清晰。它还内建了对键鼠、手柄、触屏、XR控制器等多类设备的支持也可以通过Input System Package提供的 API 来查询设备枚举和状态。新系统在Unity中启用后编辑器里会增加一个“Input Actions”资源类型你可以可视化地配置Action Map、Action、Binding、Control Scheme一键生成对应的C#脚本不用再手写一堆字符串键名。对中等规模以上的项目来说这套结构的长期收益非常明显改键不碰逻辑代码、多设备适配从“体力活”变成“配置活”。当然代价也很直接——学习曲线比老系统陡初期配置出错时排查起来也容易晕。1.3 什么时候值得切到新系统并不是所有项目都必须马上迁移。老项目已经跑得稳、团队又熟悉老API强行切换只会增加风险。如果你符合下面几个条件我建议认真考虑切换新项目刚开始还没有铺设大量输入相关代码项目需要支持手柄、触屏、键盘鼠标中的两种以上设备需要提供玩家自定义按键功能代码结构希望把输入逻辑与游戏逻辑解耦项目面临多平台发布需要应对不同平台输入差异当前版本的Unity已经对新系统做了很好的支持官方也明确表示这套系统是后续发展的方向。对一个新项目来说与其在老体系上加抽象层不如直接用官方提供的“正确姿势”起跑。2. 安装与切换从老系统到新系统只差三步2.1 通过Package Manager安装Unity从2019.1版本开始就能通过Package Manager安装新的Input System包。通常你不需要手动搜索包名新建项目时如果勾选了对应模板可能已经带上了这个包。如果没有打开Window Package Manager在包列表里找到Input System点击安装就行。记得版本尽量选择官方最新稳定版避免老版本里一些已经修复的bug影响排查判断。安装完成以后Unity会让你确认是否启用新输入系统并重启编辑器。这一步要做的是在Player Settings里切换输入处理后端。2.2 切换项目后端打开Edit Project Settings Player找到Active Input Handling选项。它有四个选项Input Manager (Old)、Input System (New)、Both和Input Manager (Old) Input System (New)不同Unity版本命名略有差异。直接切到Input System (New)会禁用老系统的所有API如果项目里还有大量老代码会导致编译报错。建议在“切换后不着急删除老代码”的情况下使用Both这样新旧API都能用。我的习惯是什么对全新项目我直接选Input System (New)彻底避免两套系统同时存在的隐性问题。对需要时间迁移的老项目先选Both但给自己设定一个“迁移剪刀”时限比如两周内把所有输入代码替换干净之后切回New防止两边混用带来的精神负担。注意切换后需要重启编辑器才能生效。这个比较容易被忽略我见过不止一次有人切完没重启代码里using UnityEngine.InputSystem;还是报错白折腾一顿。2.3 什么时候你可以不迁移如果你的项目只做同步到单一平台的原生小游戏而且设备类型就是单一的键盘鼠标老系统在写法上确实简单直接没必要为了赶潮流承担迁移风险。但在发布到手机端、主机端、PC端等多平台时我强烈不建议再用老系统去硬扛。手机端老系统虽然也有Input.touchCount可以读触摸但到了要区分多指手势、检测长按压力或同时处理手柄按键的时候代码复杂度会急剧上升。新系统把这些能力都内置了早切早省心。3. 核心概念与第一个实操Input Action资产3.1 四个必须分清的核心概念想用好Input System先要把这几个名词搞明白Action Map动作地图一组相关的Action的集合比如“游戏玩法”地图里放移动、跳跃、攻击UI地图里放确认、取消、暂停。同一时间你只会启用一个或几个地图这样在不同交互模式下不会互相干扰。比如在UI界面里移动摇杆可能被当成导航但在游戏场景里它是控制角色方向。Action动作你定义的一个抽象交互意图。比如Move移动、Jump跳跃、Fire攻击。Action本身不关心具体按键它只知道自己会被某个Binding触发。Binding绑定把Action和具体输入设备元素关联起来。比如Move绑定到键盘的WASD或手柄的左摇杆这里可以设置多条Binding让多个设备都能触发同一个动作。Control Scheme控制模式定义一组设备类型比如“键盘鼠标”是一套方案“手柄”是一套方案触摸屏是另一套。玩家所处的状态会决定哪套方案生效这样方便做到不同显示和逻辑下使用不同按键配置。这几个概念之间的层级关系是Project里有一个Input Action资产资产里包含多个Action Map每个Map里有多个Action每个Action有多个BindingBinding可以被归类到某个Control Scheme下。3.2 创建并配置一个Input Action资产在Unity中实际操作一下。右键Project窗口选择Create Input Actions给它起个名字比如叫GameInput。双击打开编辑器窗口你会看到一个类似可视化配置工具的界面。左侧是Action Maps列表点击“”新建一个Map我习惯叫它Gameplay。在这个Map下面创建几个常用的ActionAction名Action类型默认绑定MoveValueVector2WASD组合键 / 左摇杆JumpButton空格键 / 手柄南键LookValueVector2鼠标delta / 右摇杆PauseButtonEscape / 手柄Start创建Binding的时候右侧面板可以点击“Add Binding”然后在下拉列表里选择类型。键盘WASD这种多个分散按键需要分别创建比如给Move添加四个Binding每个Binding选择“Up”“Down”“Left”“Right”方向也可以选择WASD Composite模式直接把四个键合成为一个复合绑定。对于手柄支持Binding窗口下方的“Path”可以直接选择设备元素比如Gamepad/leftStick。注意选择后可以在“Control Scheme”里勾选适用的设备方案避免在键鼠模式下误触到手柄输入。我通常先定义键盘鼠标的方案再加手柄方案最后再设置触摸方案这样每个设备的绑定路径清晰明了。3.3 我为什么要给你强调“复合绑定”复合绑定Composite Binding是新系统比老系统舒服很多的地方之一。老系统里同时读WASD四个键做向量移动你得自己拼一个2D向量而且按键冲突时还得考虑“同时按下W和S会怎样”这种细节。在新系统里我可以直接添加一个2D Vector Composite然后依次指定上、下、左、右四个方向的按键系统会自动把不同方向组合成二维向量你的代码里只需要读取这个指向即可。实战经验如果你把同一个按键同时绑到两个Action上运行时会出现冲突警告或事件互相干扰。举个例子把空格同时定义为Jump和Roll而代码里两个Action都用了performed回调玩家按一下空格两个Action都会被触发。解决办法是只让一个Action绑定这个按键如果非要复用键就要进入Action的高级设置调整Overrides或使用优先级逻辑但成本较高。前端能通过UI设计规避就提前规避。4. 代码读取输入PlayerInput组件与生成的C#类4.1 两种读取方式怎么选配置好Action资产后你需要在代码里读取输入。Input System提供了两种主流方式第一种是使用PlayerInput组件。把组件挂到玩家角色上然后指定刚才创建的Input Action资产组件会自动监听对应的动作。你可以在组件面板里通过Events回调把动作事件直接拖到UnityEvent上或者通过Send Messages让组件自动调用角色脚本里同名方法。第二种是“生成C#类”。选中Input Action资产在Inspector面板底部勾选Generate C# Class指定生成的类名和命名空间然后Apply。Unity会自动生成一个封装类。比如我的资产叫GameInput生成的类就会有一个GameInput实例里面有Gameplay.Move、Gameplay.Jump这样的子类属性。代码里先实例化再绑定回调启动时执行Enable()切换地图时调用Disable()和Enable()。对于单个角色使用输入PlayerInput组件直接好用。但对于共享输入、需要全局事件分发、或者你的项目做了ECS/自定义架构我倾向生成C#类因为它不依赖MonoBehaviour生命周期在任何地方都能new出来并且订阅回调控制更精细。4.2 生成C#类的完整流程看清楚这个流程它比你想的更简单但很多人会在中间卡一步。在Project窗口选中GameInput资产在Inspector底部找到Generate C# Class复选框勾上展开它下面的C# Class File、C# Class Name、C# Class Namespace设置项可以自定义类名比如InputActions点击ApplyUnity会在同目录下生成一个脚本文件打开脚本你会看到代码里用到了InputActionAsset、InputActionMap、InputAction这些类型文件顶部会自动引用UnityEngine.InputSystem生成之后你的项目启动时可以通过如下方式直接实例化using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { private InputActions inputActions; private void Awake() { inputActions new InputActions(); } private void OnEnable() { inputActions.Gameplay.Enable(); } private void OnDisable() { inputActions.Gameplay.Disable(); } private void OnDestroy() { inputActions.Dispose(); } }这里有几个细节点值得注意生成的类实现了IDisposable最好在销毁时调用Dispose()释放内部资源。启用和禁用Action Map是很常见的打开/关闭输入通道的方法必须写清楚否则切场景后依然有输入回调活跃容易产生“人已经不在场景里却还能触发角色动画”的诡异bug。4.3 一个可跑的移动与跳跃示例咱们来写一段最简单也很实用的移动跳跃代码直接展示在游戏里怎么使用生成类using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { [SerializeField] private float moveSpeed 5f; [SerializeField] private float jumpForce 5f; [SerializeField] private Rigidbody rb; private InputActions inputActions; private Vector2 moveInput; private bool isGrounded true; private void Awake() { inputActions new InputActions(); } private void OnEnable() { inputActions.Gameplay.Enable(); inputActions.Gameplay.Move.performed OnMove; inputActions.Gameplay.Move.canceled OnMove; inputActions.Gameplay.Jump.performed OnJump; } private void OnDisable() { inputActions.Gameplay.Move.performed - OnMove; inputActions.Gameplay.Move.canceled - OnMove; inputActions.Gameplay.Jump.performed - OnJump; inputActions.Gameplay.Disable(); } private void OnMove(InputAction.CallbackContext context) { moveInput context.ReadValueVector2(); } private void OnJump(InputAction.CallbackContext context) { if (isGrounded) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGrounded false; } } private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag(Ground)) { isGrounded true; } } private void Update() { Vector3 move new Vector3(moveInput.x, 0, moveInput.y); transform.position move * moveSpeed * Time.deltaTime; } }这段代码里我从Move.performed和canceled回调里读取Vector2输入分别对应摇杆或WASD的按下与松开再把向量应用到角色位移。Jump.performed只在按钮“按下”的那一帧触发一次正好适合跳跃这种脉冲式动作。实测注意不要漏了在OnDisable里解绑事件。Unity中脚本被禁用时事件依然会挂在生成类的实例上如果不解绑再启用时会重复订阅导致每次按下按键执行多次跳跃。这个坑我踩过表现是角色跳一下变成二连跳排查了一圈才发现回调重复订阅了。4.4 Action类型与回调时机Value和Button的区别配置Action时你会看到“Action Type”下拉选项通常有Button和Value两种。它们对应完全不同的回调节奏。Button类型只有按钮按下和松开两个状态触发performed时表示“按下”。Value类型返回一个连续变化的输入值比如摇杆轴、鼠标移动量、触摸位置触发performed时可以读取到一个动态数值。回调时机也要区分清楚。一个Button的Action在“按下”那一帧触发started和performed松开时触发canceled。在按键按住不放的过程中它并不会反复触发。而Value类型在你的连续输入值发生变化时触发performed如果你按住方向键不动这个数字可能稳定在一个值上不再触发回调。理解这一点是排查“为什么我的按键只触发一次”和“为什么松开后居然还有输入量”的关键。5. 常见问题排查与避坑心得5.1 启用新系统后老代码报错这是切换后最常见的现象。老代码里大量使用UnityEngine.Input类新系统默认后老API被禁用编译器告诉你找不到Input。解决办法是在Project Settings里把Active Input Handling切到Both然后在迁移过程中逐步替换。如果你是新建项目切到New则不要回头看老API直接改用新写法。5.2 我的角色为什么一直往某个方向飘排查思路先检查Action资产里的Move是否被误绑定了多个设备。比如给键盘绑定了WASD同时给手柄绑定左摇杆但手柄的摇杆中点在轻微漂移很多手柄的摇杆会有微小死区偏差导致系统认为一直有输入。此时需要给Binding设置Dead Zone处理器或者给摇杆轴设置合理的Dead值。Unity的Binding面板里可以加Processors选择一个死区处理器即可。死区并不是“灵敏度”的概念它是一个“低于某个绝对值就视为0”的过滤器能让轻微抖动被忽略。5.3 为什么手柄按键没有响应新手最容易忽略Control Scheme的作用。如果Action只在“KeyboardMouse”方案里启用了手柄按键触发就不会匹配到它。在Input Action资产窗口里右侧Panel可以勾选手柄端口对应的方案然后把手柄Binding挂到该方案下面。另外有些手柄在PC上会识别成Gamepad有些老式手柄会识别为Joystick两者路径不同注意查看设备事件日志。5.4 鼠标点击和UI响应奇怪新系统里鼠标点击和UI交互默认走UI模块的独立输入路径。如果你在3D场景里用自定义射线去检测点击注意读取鼠标位置时使用Mouse.current.position.ReadValue()并且不要在UI遮挡物体时继续响应。此外启用新系统后Standalone Input Module组件需要匹配新的事件系统Unity的UI会自动适配但你如果是手动创建的EventSystem记得检查组件引用的模块是否为InputSystemUIInputModule否则点击UI毫无反应。5.5 多玩家或动态切换角色的输入归属如果你做的是本地多人同屏新系统提供了PlayerInputManager可以管理多个玩家实例每个玩家可以绑定不同的设备。但如果只是同一设备在不同场景给不同角色用手动切换目标是更轻量的方案。我会给每个角色挂一个输入监听脚本在切换到该角色时调用Enable()切走时调Disable()避免两个角色同时响应同一个输入。不要贪图方便让所有角色都常驻启用输入监听那样角色会“互相抢”输入。5.6 不要在Update里轮询回调事件有些朋友写了if (Keyboard.current.spaceKey.wasPressedThisFrame)这种代码在Update里每帧检测然后去订阅动作结果出现事件重复触发或时序不对。新系统的推荐用法是直接在回调里处理事件不要在Update里把事件转存为标志位再处理。如果你真的需要“每帧检查状态”用Action的读值APIWasPressedThisFrame()/WasReleasedThisFrame()或者ReadValue()时才正确。6. 迁移建议与我的个人心得从老系统迁移到新系统最忌“一把梭”。我给一个稳妥的迁移顺序先把项目所有输入读取集中到一个接口类哪怕是临时包装老API也行然后把外部代码对这个接口类的依赖理清最后才把接口类内部实现切换到Input System。这样外层逻辑不用动内部实现替换是可控的。新系统学习过程中我建议多动手在编辑器里配置Action资产别一上来就复制代码。因为很多所谓“代码没用”的问题其实是没有正确配置Binding。Asset面板里每一块配置对应一个运行时行为你把WASD绑定错了代码写得再漂亮也白搭。我个人实际项目里的体会是新系统真正降低的不是写输入代码的难度而是多设备、多平台需求下的维护成本。以前写着写着就乱掉的输入拼接逻辑现在全部变成了资源和配置层面的东西。刚开始两天确实会觉得配置比写代码还繁琐但一个礼拜之后就会习惯这种“可视化事件映射”的工作方式。再有就是调试设备的好习惯在Game窗口打开Window Analysis Input Debugger可以看到当前Unity到底检测到哪些设备每个设备的输入值变化是否正常。这几乎是我排查一切输入问题的第一站。手柄不响应先看Debugger里的设备有没有名字按键没反映看节点状态是否变化摇杆漂移看数值曲线。比在游戏里反复试按快多了。这篇文章没打算把Input System所有细节都讲完毕竟这只是第一讲。核心目标是把“为什么要换”“怎么安装”“第一个输入动作怎么做”“代码里的正确姿势”“最常见的坑在哪”这几件事说明白。后续如果想聊深一点我打算按照“复杂交互——Action的Interactions配置”“自定义处理器与绑定适配”“多玩家与手柄热插拔”“输入调试全技巧”这几个方向逐个展开。到时候再把那些具体的参数表、快捷键和真实踩坑案例一起摆出来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑