3.1 输入系统:玩家指令的收发窗口


文档摘要

3.1 输入系统:玩家指令的收发窗口 本节摘要:输入系统是玩家与游戏之间唯一的收发窗口。本节对比 Unity 的旧 Input Manager 与新 Input System 两套方案,给出各自的最小接入写法与选型建议,并演示"按键、长按、双击"三种典型输入的判定实现。 两套输入系统怎么选 Unity 同时保留两套输入方案,新项目绕不开选型。旧的 Input Manager(代码里写 Input.GetKey)胜在零配置、上手快,读一个键就是一行代码,网上九成老教程都基于它;新的 Input System 包胜在抽象能力强——同一个"跳跃"动作可以同时绑定键盘空格、手柄 A 键与触屏按钮,换设备不改逻辑,还自带事件回调模式。

3.1 输入系统:玩家指令的收发窗口

本节摘要:输入系统是玩家与游戏之间唯一的收发窗口。本节对比 Unity 的旧 Input Manager 与新 Input System 两套方案,给出各自的最小接入写法与选型建议,并演示"按键、长按、双击"三种典型输入的判定实现。

两套输入系统怎么选

Unity 同时保留两套输入方案,新项目绕不开选型。旧的 Input Manager(代码里写 Input.GetKey)胜在零配置、上手快,读一个键就是一行代码,网上九成老教程都基于它;新的 Input System 包胜在抽象能力强——同一个"跳跃"动作可以同时绑定键盘空格、手柄 A 键与触屏按钮,换设备不改逻辑,还自带事件回调模式。旧的官方已不再演进,新的需要安装包并在项目设置里把活动输入处理切到新系统(两者并存也可以,但容易写出两套互相打架的读取)。

我的建议按项目规模分:课程作业、原型验证、单人 PC 小品,用旧接口最快,不为省一周工时引入迁移债;预计要上手机或主机、要支持手柄重映射、要做本地双人,一开始就上 Input System——它的事件模型与设备抽象在多平台场景下是碾压性的。中途换系统是可行的,但所有输入代码要过一遍手,成本不低,能预先定就不拖。

旧接口的三种读取

旧 Input 按读取意图分三类,对应三种手感需求:

using UnityEngine; public class LegacyInputReader : MonoBehaviour { void Update() { // 一、连续状态:按住就为真,用于持续移动 float h = Input.GetAxis("Horizontal"); // 已做重力平滑,-1 到 1 float v = Input.GetAxisRaw("Vertical"); // Raw 无平滑,适合追求即时响应 // 二、边沿检测:只在乎"按下这一帧" if (Input.GetKeyDown(KeyCode.Space)) { Debug.Log("跳跃指令(只触发一次)"); } // 三、释放检测:松手瞬间触发,蓄力跳的收尾 if (Input.GetKeyUp(KeyCode.Space)) { Debug.Log("蓄力结束"); } if (h != 0f || v != 0f) { Debug.Log("移动轴向 " + h + " " + v); } } }

三者的差别是面试与实战都爱考的点:GetKey 系列按住每帧为真,GetKeyDown 只在按下那一帧为真,GetKeyUp 只在松开那一帧为真。连续移动用 Get 系,单次触发用 Down 系,蓄力释放用 Up 系——混用就会出"跳跃键按住连跳"之类的经典 bug。Axis 的平滑版本带惯性(数值渐变),Raw 是阶跃(立即到头),平台跳跃这类需要精确手感的项目通常用 Raw 再自己做加速曲线。

新系统的动作绑定

Input System 的核心抽象是"动作(Action)":先定义语义(跳跃、攻击、移动),再把物理按键绑上去,代码只认动作不认键。最小接入如下:

using UnityEngine; using UnityEngine.InputSystem; public class NewInputReader : MonoBehaviour { // 场景里放一个 PlayerInput 组件或代码里直接监听 void OnEnable() { // 直接订阅键盘设备的简写写法 Keyboard.current.spaceKey.performed += OnJump; Keyboard.current.spaceKey.canceled += OnJumpEnd; } void OnDisable() { // 有订阅必有退订,与事件纪律一致 Keyboard.current.spaceKey.performed -= OnJump; Keyboard.current.spaceKey.canceled -= OnJumpEnd; } void OnJump(UnityEngine.InputSystem.InputAction.CallbackContext ctx) { // performed 表示动作被完整触发 Debug.Log("新系统:跳跃动作触发"); } void OnJumpEnd(UnityEngine.InputSystem.InputAction.CallbackContext ctx) { Debug.Log("新系统:跳跃键释放"); } }

更工程化的路线是建一个"输入动作资产",在编辑器里可视化配置动作表,PlayerInput 组件自动分发回调。这里点到为止,重点是建立"语义与按键分离"的意识:将来策划要求"把跳跃从空格改成 K 键",改的是资产配置,代码零改动。

动手:蓄力跳跃的三段判定

背景:平台跳跃主角需要蓄力跳——按下开始蓄力,松开起跳,蓄力时长决定跳高。这需要 Down、持续、Up 三个时点协同。

操作:在角色脚本里实现状态记录,物理执行留到第 3.2 节的 FixedUpdate 岗:

using UnityEngine; public class ChargeJumpInput : MonoBehaviour { public float minCharge = 0.2f; // 最短蓄力 public float maxCharge = 1.0f; // 满蓄力 bool charging; float chargeStart; void Update() { if (Input.GetKeyDown(KeyCode.Space)) { charging = true; chargeStart = Time.time; } // 持续阶段:只记录,不触发 if (charging && Input.GetKey(KeyCode.Space)) { float held = Time.time - chargeStart; if (held > maxCharge) held = maxCharge; // 蓄满封顶 } if (charging && Input.GetKeyUp(KeyCode.Space)) { float power = (Time.time - chargeStart) / maxCharge; if (power < minCharge) power = minCharge; Debug.Log("起跳力度 " + power.ToString("F2")); charging = false; // 把 power 存进变量,交给 FixedUpdate 里的物理执行 } } }

结果:快速点按空格,力度稳定在 0.2;按住约一秒松开,力度达到 1.0;Console 依次输出力度值。

解读:这个案例展示了输入岗的正确姿势——Update 只负责采集与判定,把"结果"存成字段;真正让角色跳起来的代码放在物理班次。这样即使帧率从 144 掉到 30,蓄力判定依旧准确,因为判定基于 Time.time 的真实时间差而非帧数。另一个细节是封顶处理:不给 maxCharge 上限,按住十分钟的玩家会得到离谱数值——所有来自输入的原始数值都要经过钳制再进入游戏逻辑。

变式:给蓄力加一个"可打断"规则:蓄力中按 Shift 立即取消。实现只需在取消分支里把 charging 置回 false 并清空力度字段——状态机思维在输入处理里随处可见,先有状态再有转移,代码自然清晰。

移动端与手柄的统一

同一套语义映射到不同设备,是输入抽象的最大回报:触屏的虚拟摇杆输出与键盘一样的轴向,手柄的左摇杆、扳机键各自绑到移动与攻击动作上。旧接口下这需要 per-device 分支代码,新系统下只是资产里多几条绑定。提前规划动作表(移动、跳跃、交互、暂停四个起步动作),后期接设备就不需要重构。

本节要点回顾

  • 两套系统一选一:小项目旧接口快,多平台新系统稳,中途迁移有成本;
  • Down、Get、Up 三兄弟:边沿触发、持续状态、释放触发,混用出连跳 bug;
  • Axis 与 Raw:带平滑与阶跃两版,手感要求高就用 Raw 自己做曲线;
  • 语义与按键分离:新系统以动作表为中枢,换绑不改代码;
  • 输入岗只采集:判定结果存字段,物理执行留给 FixedUpdate。

指令已经变成意图,下一节让物理岗接手:刚体、重力与那个最容易写错班次的移动代码。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U