手把手拆解Unity新输入系统:从零到懂的完整指南
前言:为什么你会"不懂"?
如果你读完前面的介绍文章还是一头雾水,别自责——这不是你的问题,是新输入系统真的很绕。
它引入了太多新概念:Device、Action、Binding、Action Map、Control Scheme、Composite、Interaction、Processor……这些词单独看都能理解,但它们怎么串在一起、每个到底做什么、代码到底该怎么写——教程里往往一笔带过,让人云里雾里。
今天,我们抛开高大上的架构描述,用最具体的例子,一步步把每个概念拆开揉碎讲清楚。看完这篇文章,你不仅能懂,还能自己动手写。
第一步:先把新系统装上
新输入系统不是Unity内置的,你需要手动安装:
- 打开Window > Package Manager
- 左上角切换到Unity Registry
- 搜索“Input System”
- 点击Install
安装后Unity会弹窗问你:"要切换到新输入系统吗?"选Yes。Unity会重启,之后旧的Input.GetKey就失效了(除非你选择"Both"模式同时启用两套)。
设置位置在:Edit > Project Settings > Player > Active Input Handling
- Input Manager (Old):只用旧系统
- Input System Package (New):只用新系统
- Both:两套同时启用(推荐初学者过渡时选这个)
第二步:认识那个最简单的例子
我们从一个最原始的例子开始——按空格键让方块跳起来。
在旧系统里,代码是这样的:
voidUpdate(){if(Input.GetKeyDown(KeyCode.Space)){rb.AddForce(Vector3.up*5,ForceMode.Impulse);}}在新系统里,最"直译"的写法是这样的:
usingUnityEngine.InputSystem;voidUpdate(){if(Keyboard.current.spaceKey.wasPressedThisFrame){rb.AddForce(Vector3.up*5,ForceMode.Impulse);}}看到区别了吗?
Input.GetKeyDown(KeyCode.Space)→Keyboard.current.spaceKey.wasPressedThisFrame- 更长,但每一部分都是类型安全的:
Keyboard.current是"当前键盘设备",.spaceKey是"空格键控件",.wasPressedThisFrame是"本帧是否按下"。
IDE会给你完美的自动补全,编译器会检查拼写错误。这是新系统最"贴地"的用法——直接读取设备状态。
其他常用写法:
// 键盘Keyboard.current.aKey.isPressed// A键是否按住Keyboard.current.wKey.wasReleasedThisFrame// W键本帧是否松开// 鼠标Mouse.current.leftButton.wasPressedThisFrame// 左键本帧是否按下Mouse.current.position.ReadValue()// 鼠标位置(Vector2)Mouse.current.scroll.ReadValue()// 滚轮增量(Vector2)// 手柄Gamepad.current.buttonSouth.wasPressedThisFrame// A/×键Gamepad.current.leftStick.ReadValue()// 左摇杆(Vector2)Gamepad.current.rightTrigger.ReadValue()// 右扳机(float)如果你只是想快速上手,写到这里就已经够用了。但新系统的真正威力,在于下面要讲的Action系统。
第三步:理解Action的核心思想
Action(动作)是新系统的灵魂。要理解它,先看一个痛点:
假设你在角色控制脚本里写了:
if(Keyboard.current.spaceKey.wasPressedThisFrame)Jump();现在需求变了:
- 玩家想用手柄A键跳跃 → 你要改代码加一个判断
- 玩家想自定义按键 → 你要写一整套映射管理
- 手机版要用屏幕按钮跳跃 → 你又要加一个分支
代码里写死了"按什么键",就意味着以后每次改按键都要改代码。
Action的思想是:代码不关心"按了什么",只关心"想做什么"。
// 代码只声明"我关心一个叫Jump的动作"jumpAction.performed+=ctx=>Jump();至于Jump到底对应空格键、手柄A键、还是屏幕按钮——那是配置的事,不是代码的事。这就是从"面向按键"到"面向意图"的转变。
第四步:创建你的第一个InputActionAsset
现在我们来实际操作。
1. 创建资产:
在Project窗口右键 →Create > Input Actions,命名为PlayerControls。
2. 双击打开编辑器,你会看到一个三列布局:
- 左列:Action Maps(动作图)
- 中列:Actions(动作)
- 右列:Properties(属性)
3. 创建Action Map:
点击左列的+,命名为Gameplay(表示"游戏玩法阶段"的输入配置)。
4. 创建Action:
在中列点+,创建一个叫Jump的Action。
5. 添加Binding(绑定):
展开Jump,看到一个<No Binding>。点它,右边Properties里点Path旁边的按钮,选择Keyboard > Space。
再点Jump右侧的+,选Add Binding,再绑定到Gamepad > Button South(这样A键和手柄A键都能触发跳跃)。
6. 保存:
点击左上角的Save Asset。
现在,你有了一个叫Jump的动作,它同时绑定到键盘空格键和手柄A键。注意,你的代码里还没有出现任何"空格键"或"A键"的字样——绑定完全在资产里。
第五步:在代码里使用Action
有了资产,怎么用呢?有三种典型方式,从简单到强大:
方式一:直接引用资产(最简单)
在角色脚本里:
usingUnityEngine;usingUnityEngine.InputSystem;publicclassPlayerController:MonoBehaviour{publicInputActionAssetcontrols;// 拖入PlayerControls资产privateInputActionjumpAction;voidOnEnable(){// 找到Gameplay Action Map里的Jump动作jumpAction=controls.FindActionMap("Gameplay").FindAction("Jump");jumpAction.performed+=OnJump;// 订阅事件jumpAction.Enable();// 启用动作}voidOnDisable(){jumpAction.performed-=OnJump;jumpAction.Disable();}voidOnJump(InputAction.CallbackContextctx){Debug.Log("跳跃!");// 执行跳跃逻辑}}关键点:
OnEnable里订阅事件,OnDisable里取消订阅——避免脚本销毁后回调还在跑Enable()必须调用,否则Action不会触发InputAction.CallbackContext包含了这次触发的详细信息(触发时间、输入值等)
方式二:使用PlayerInput组件(最推荐)
这是Unity官方最推荐的方式,让新手也能快速上手:
1. 给你的角色GameObject挂上PlayerInput组件(在AddComponent里搜索)
2. 在PlayerInput组件里配置:
- Actions:拖入
PlayerControls资产 - Default Map:选
Gameplay - Behavior:选
Invoke Unity Events(新手最友好的模式)
3. 展开Events,找到你的Action(比如Jump),点+添加回调,绑定到脚本的方法:
publicclassPlayerController:MonoBehaviour{// 在Inspector里把这个方法拖进Jump事件publicvoidOnJump(InputAction.CallbackContextctx){if(ctx.performed){Debug.Log("跳跃!");}}}这种方式的好处是:回调完全在Inspector里配置,代码里连Action的名字都不用出现。策划改按键、程序不需要动代码。
方式三:生成C#类(最优雅)
在InputActionAsset的Inspector里,勾选Generate C# Class,Unity会自动生成一个和你的资产对应的C#类:
usingUnityEngine;publicclassPlayerController:MonoBehaviour{privatePlayerControlscontrols;// 自动生成的类voidAwake(){controls=newPlayerControls();controls.Gameplay.Jump.performed+=ctx=>Jump();}voidOnEnable()=>controls.Gameplay.Enable();voidOnDisable()=>controls.Gameplay.Disable();voidJump(){Debug.Log("跳跃!");}}看那句controls.Gameplay.Jump.performed——完全类型安全,IDE有完整的自动补全,重构工具能安全重命名。这才是新系统的完全体。
第六步:那个让人困惑的CallbackContext
每个Action回调都有一个InputAction.CallbackContext ctx参数。它到底是什么?
voidOnJump(InputAction.CallbackContextctx){ctx.phase// 当前阶段:Started / Performed / Canceledctx.performed// 是不是Performed阶段ctx.started// 是不是Started阶段ctx.canceled// 是不是Canceled阶段ctx.ReadValue<float>()// 读取输入值(比如扳机压力)ctx.ReadValue<Vector2>()// 读取二维输入(比如摇杆)ctx.control// 触发的具体控件(比如"space key")ctx.time// 触发时间戳ctx.duration// 已按住的时长}为什么要有started、performed、canceled三个阶段?
想象一个"长按0.5秒开火"的技能:
- Started:玩家刚按下按键的瞬间(0秒时)
- Performed:按住满0.5秒,技能触发(0.5秒时)
- Canceled:玩家松开或不满足条件(比如提前松开)
对于普通的"按一下跳跃",三个阶段几乎同时触发,你只需要在performed里处理。但对于长按、连击、蓄力等复杂输入,这三个阶段就至关重要了。
第七步:读取移动输入的正确姿势
WASD控制移动是最常见的需求。看看新系统怎么优雅地做到:
1. 在资产里创建Move Action,Action Type选Value,Control Type选Vector2
2. 添加Binding时,选Add 2D Vector Composite,会自动展开成四个子绑定:
Move (Vector2) ├── Up: <Keyboard>/w ├── Down: <Keyboard>/s ├── Left: <Keyboard>/a └── Right: <Keyboard>/d3. 代码里读取:
publicvoidOnMove(InputAction.CallbackContextctx){Vector2dir=ctx.ReadValue<Vector2>();// dir.x 是水平方向 (-1到1)// dir.y 是垂直方向 (-1到1)}或者在Update里持续读取:
voidUpdate(){Vector2dir=moveAction.ReadValue<Vector2>();transform.Translate(newVector3(dir.x,0,dir.y)*speed*Time.deltaTime);}这就是Composite的威力——把四个按键"编织"成一个二维向量,代码里拿到的就是清爽的Vector2。
第八步:Interaction让复杂输入变简单
新系统内置了多种交互模式,一个最实用的例子:长按蓄力。
1. 在资产里,选中你的Fire Action的Binding
2. 在右边Properties里,找到Interactions,点+,选Hold
3. 设置Hold Time= 0.5
4. 代码里:
publicvoidOnFire(InputAction.CallbackContextctx){if(ctx.started)Debug.Log("开始蓄力");if(ctx.performed)Debug.Log("蓄力完成,发射!");if(ctx.canceled)Debug.Log("取消或释放");}不需要写任何计时器代码,Interaction帮你处理了所有时序逻辑。想改成双击?换成MultiTap就行。想改成慢速按下?换成SlowTap。
第九步:Action Type到底选哪个?
创建Action时,你会面对三个选项:
- Value:读取"当前值",适合摇杆、扳机、鼠标位置这种持续变化的输入。会自动应用Disambiguation(多设备优先级)。
- Button:按钮式输入,适合跳跃、开火这种离散触发。当值超过阈值就触发performed。
- Pass Through:不做任何过滤,所有输入变化都通过。适合特殊需求,比如同时监听多个设备。
记忆口诀:读值用Value,按键用Button,除非特殊需求,别碰Pass Through。
尾声:从"看不懂"到"用得爽"
现在,让我们回顾一下你已经掌握的核心概念:
- Device:物理输入设备(Keyboard.current这种)
- Action:逻辑动作(Jump、Move、Fire)
- Binding:动作到具体按键的绑定
- Action Map:一组Action的集合(Gameplay / UI)
- Composite:把多个按键组合成一个复合输入(WASD → Vector2)
- Interaction:交互模式(Hold、Tap、MultiTap)
- CallbackContext:回调事件的详细信息(phase、value、time)
以及三种使用方式:
- 直接查设备:
Keyboard.current.spaceKey.wasPressedThisFrame—— 快速简单 - PlayerInput组件:在Inspector里配置回调 —— 新手友好
- 生成C#类:类型安全的强大写法 —— 大型项目首选
看不懂新输入系统,是因为它引入了一层"抽象"——从物理按键到逻辑意图的抽象。一旦你翻过这堵墙,你会发现它带来的灵活性和扩展性,是旧系统望尘莫及的。
现在,打开Unity,创建一个InputActions资产,动手试一试吧。十分钟的实践,胜过一小时的阅读。当你亲手让空格键和手柄A键同时触发跳跃时,那种"啊,原来是这样"的顿悟感,会让所有这些概念在你脑海里瞬间连成一片。
祝你玩得开心。