ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

手把手拆解Unity新输入系统:从零到懂的完整指南

2026/8/7 9:47:58 拓冰建站 浏览量
手把手拆解Unity新输入系统:从零到懂的完整指南

前言:为什么你会"不懂"?

如果你读完前面的介绍文章还是一头雾水,别自责——这不是你的问题,是新输入系统真的很绕

它引入了太多新概念:Device、Action、Binding、Action Map、Control Scheme、Composite、Interaction、Processor……这些词单独看都能理解,但它们怎么串在一起、每个到底做什么、代码到底该怎么写——教程里往往一笔带过,让人云里雾里。

今天,我们抛开高大上的架构描述,用最具体的例子,一步步把每个概念拆开揉碎讲清楚。看完这篇文章,你不仅能懂,还能自己动手写。

第一步:先把新系统装上

新输入系统不是Unity内置的,你需要手动安装:

  1. 打开Window > Package Manager
  2. 左上角切换到Unity Registry
  3. 搜索“Input System”
  4. 点击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>/d

3. 代码里读取:

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键同时触发跳跃时,那种"啊,原来是这样"的顿悟感,会让所有这些概念在你脑海里瞬间连成一片。

祝你玩得开心。