# 01 · 输入系统 > **命名空间** `BaseGames.Input` > **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md) > **依赖** Unity Input System · `BaseGames.Core.Events` --- ## 目录 1. [设计目标](#1-设计目标) 2. [核心架构:InputReaderSO](#2-核心架构inputreaderso) 3. [Input Actions 资产结构](#3-input-actions-资产结构) 4. [InputBuffer — 输入缓冲](#4-inputbuffer--输入缓冲) 5. [Coyote Time](#5-coyote-time) 6. [输入数据流](#6-输入数据流) 7. [设备切换与多平台支持](#7-设备切换与多平台支持) 8. [编辑器友好设计](#8-编辑器友好设计) --- ## 1. 设计目标 - **零耦合**:游戏系统不直接引用 `PlayerInput` 组件,通过 `InputReaderSO`(ScriptableObject)订阅输入事件 - **可测试**:`InputReaderSO` 可在测试代码中手动触发事件,无需模拟硬件 - **可重映射**:Input Actions Asset 支持运行时重映射,配合 UI 设置页面 - **输入宽容**:提供输入缓冲(Buffer)和 Coyote Time,提升手感 --- ## 2. 核心架构:InputReaderSO `InputReaderSO` 是整个输入系统的**唯一门面**,封装 Input Actions,以 C# Action 事件对外暴露: ``` InputReaderSO (ScriptableObject) │ ├── 内部持有: PlayerInputActions (生成的 C# 类) │ ├── 移动输入 │ ├── event MoveEvent(Vector2 direction) │ └── Vector2 MoveInput { get; } ← 当前帧移动向量(持续值) │ ├── 跳跃输入 │ ├── event JumpStartedEvent() ← 按下(用于触发跳跃) │ └── event JumpCancelledEvent() ← 松开(用于可变跳跃高度) │ ├── 攻击输入 │ └── event AttackEvent() ← 按下 │ ├── 弹反输入 │ └── event ParryEvent() ← 按下 │ ├── 冲刺输入 │ └── event DashEvent() ← 按下 │ ├── 灵泉输入 │ └── event UseSpringEvent() ← 按下(消耗灵泉使用次数) │ ├── 形态切换输入 │ ├── event SwitchSkyFormEvent() ← 切换天魂姿态 │ ├── event SwitchEarthFormEvent() ← 切换地魂姿态 │ └── event SwitchDeathFormEvent() ← 切换命魂姿态 │ ├── 技能输入 │ ├── event SoulSkillEvent() ← 当前形态魂技能(消耗灵力) │ ├── event SpiritSkill1StartedEvent() ← 魄技能 1 按下 │ ├── event SpiritSkill1CancelledEvent() ← 魄技能 1 松开(蓄力型技能用) │ ├── event SpiritSkill2StartedEvent() ← 魄技能 2 按下 │ └── event SpiritSkill2CancelledEvent() ← 魄技能 2 松开(蓄力型技能用) │ ├── 交互输入 │ └── event InteractEvent() ← 按下 │ ├── UI 输入 │ ├── event PauseEvent() │ ├── event NavigateEvent(Vector2 dir) │ └── event SubmitEvent() │ └── Action Map 切换 ├── EnableGameplayInput() ← 进入游戏时启用 ├── EnableUIInput() ← 进入 UI 时启用 └── DisableAllInput() ← 过场动画/加载时禁用 ``` **所有使用输入的系统**仅需在 Inspector 中拖入 `InputReaderSO` 资产,订阅所需事件: ``` 系统 A: [SerializeField] InputReaderSO _input; OnEnable → _input.JumpStartedEvent += HandleJump; OnDisable → _input.JumpStartedEvent -= HandleJump; ``` --- ## 3. Input Actions 资产结构 `PlayerInputActions.inputactions` 包含以下 Action Maps: ### Gameplay Action Map | Action 名称 | 类型 | 绑定(默认键盘)| 绑定(手柄)| |------------|------|---------------|------------| | `Move` | Value (Vector2) | WASD / Arrow Keys | 左摇杆 | | `Jump` | Button | Space | South Button (×/A) | | `Attack` | Button | J / Z | West Button (□/X) | | `Parry` | Button | K / X | Right Bumper (R1/RB) | | `Dash` | Button | L-Shift / C | East Button (○/B) | | `UseSpring` | Button | G / Tab | Right Trigger (R2/RT) | | `Interact` | Button | F / E | North Button (△/Y) | | `SwitchSkyForm` | Button | 1 | D-Pad Left | | `SwitchEarthForm` | Button | 2 | D-Pad Down | | `SwitchDeathForm` | Button | 3 | D-Pad Right | | `SoulSkill` | Button | Q | Left Trigger (L2/LT) | | `SpiritSkill1` | Button | E | D-Pad Up | | `SpiritSkill2` | Button | R | Left Trigger (L2/LT)(双击或组合)| | `Pause` | Button | Escape | Start / Menu | ### UI Action Map | Action 名称 | 类型 | 说明 | |------------|------|------| | `Navigate` | Value (Vector2) | UI 导航方向 | | `Submit` | Button | 确认 | | `Cancel` | Button | 返回 | | `Point` | Value (Vector2) | 鼠标/触摸位置(UI 点击)| ### 重映射配置 - 使用 `PlayerInput` 组件的 `SaveBindingOverridesAsJson()` 持久化重映射到 PlayerPrefs - 启动时调用 `LoadBindingOverridesFromJson()` 恢复 --- ## 4. InputBuffer — 输入缓冲 `InputBuffer` 是一个**轻量计时器**组件,解决"输入早于判断条件成立"问题: ### 缓冲时长配置 | 输入动作 | 缓冲时长 | 说明 | |---------|---------|------| | 跳跃 | 0.15s | 落地前提前按跳跃,落地即起跳 | | 攻击 | 0.12s | 前一段攻击结束前输入,自动接续下一击 | | 弹反 | 0.0s | 不缓冲(弹反必须精准,缓冲会降低挑战性)| | 冲刺 | 0.1s | 小量缓冲,避免帧率不稳定导致失手 | | UseSpring | 0.0s | 不缓冲(消耗资源,防误操作)| | SoulSkill | 0.1s | 小量缓冲,允许攻击后衔接技能 | | SpiritSkill1/2 | 0.0s | 不缓冲(蓄力型技能按下即生效)| ### 缓冲工作原理 ``` 玩家按下跳跃键 → InputBuffer 记录 jumpBufferTimer = 0.15s → 每帧 jumpBufferTimer -= deltaTime ↓ PlayerAirState 进入(落地) → 查询 InputBuffer.HasBufferedJump() ├── 若 jumpBufferTimer > 0 → true → 立即起跳 → 消费缓冲 └── 若 jumpBufferTimer ≤ 0 → false → 不起跳 ``` ### 缓冲接口 ``` InputBuffer ├── bool HasBufferedJump() → 消费性查询(调用后清除) ├── bool HasBufferedAttack() → 消费性查询 ├── bool HasBufferedDash() → 消费性查询 ├── void ConsumeJump() → 手动消费 ├── void ConsumeAttack() └── void ConsumeDash() ``` --- ## 5. Coyote Time Coyote Time 让玩家在**走出平台边缘后的短暂时间内**仍可起跳,提升平台跳跃手感: ``` 玩家离开地面 → PlayerAirState 记录 coyoteTimer = 0.12s(当离地原因是走下而非跳跃时) → 每帧 coyoteTimer -= deltaTime ↓ 玩家按下跳跃键(此时仍在 coyoteTimer > 0) → 视为地面跳跃(速度叠加正常跳跃力) → 消耗 coyoteTimer(不可再次触发) ``` **Coyote Time 不生效的情况**: - 玩家主动跳跃后进入空中(不是走落) - 已经触发过一次 Coyote Jump - 玩家正在执行冲刺(DashState 期间禁用 Coyote) | 参数 | 值 | 位置 | |------|-----|------| | `CoyoteTimeDuration` | 0.12s | `PlayerMovementConfigSO` | | `JumpBufferDuration` | 0.15s | `PlayerMovementConfigSO` | --- ## 6. 输入数据流 ``` 硬件设备(键盘/手柄) ↓ Input System Runtime ↓ PlayerInputActions(生成类,内嵌在 InputReaderSO) ↓ InputReaderSO.OnJumpPerformed() → 触发 JumpStartedEvent ↓ InputBuffer.RecordJump(timestamp) ↓ PlayerAirState.OnStateUpdate() └── InputBuffer.HasBufferedJump() → true → 起跳 ``` **Action Map 切换时序**: ``` 游戏启动 → EnableGameplayInput() 打开暂停菜单 → EnableUIInput() (游戏逻辑冻结,输入切换到 UI) 关闭暂停菜单 → EnableGameplayInput() 进入过场动画 → DisableAllInput() 过场动画结束 → EnableGameplayInput() ``` --- ## 7. 设备切换与多平台支持 `InputReaderSO` 监听 `InputSystem.onActionChange`,检测当前活跃设备类型,发布 `DeviceChangedEvent(DeviceType)`,UI 系统根据此事件切换图标(键盘图标 / 手柄图标)。 | DeviceType | 说明 | |-----------|------| | `KeyboardMouse` | 键盘 + 鼠标 | | `Gamepad` | 手柄(PS / Xbox)| | `Touch` | 触屏(移动端扩展,P2)| --- ## 8. 编辑器友好设计 ### InputReaderSO Inspector 自定义 Inspector(`BaseGames.Editor`)提供: - **实时事件监控**(Play Mode):每个 Action 上方显示"上次触发时间" - **手动触发按钮**:在 Inspector 中点击"Simulate Jump"等按钮,无需按实体键,方便调试 FSM 状态 ### Input 配置可视化 `InputBuffer` 在 Inspector 中以进度条显示各缓冲的剩余时间(只读),方便调试缓冲窗口: ``` ┌─ InputBuffer ──────────────────────────────┐ │ Jump Buffer [████░░░░░░░] 0.08s / 0.15s │ │ Attack Buffer [░░░░░░░░░░░] 0.00s / 0.12s │ │ Dash Buffer [░░░░░░░░░░░] 0.00s / 0.10s │ └────────────────────────────────────────────┘ ```