Files
zeling_v2/Docs/Design/01_InputSystem.md
2026-05-08 11:04:00 +08:00

265 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 │
└────────────────────────────────────────────┘
```