# 25 · 输入重映射 UI
> **命名空间** `BaseGames.Input`
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
> **依赖** `BaseGames.Input`(InputReaderSO)· `BaseGames.UI` · Unity Input System
---
## 目录
1. [系统总览](#1-系统总览)
2. [重映射架构](#2-重映射架构)
3. [RebindPanel — UI 面板](#3-rebindpanel--ui-面板)
4. [RebindActionRow — 单行绑定控件](#4-rebindactionrow--单行绑定控件)
5. [冲突检测](#5-冲突检测)
6. [按键名称显示(Key-to-String)](#6-按键名称显示key-to-string)
7. [持久化存储](#7-持久化存储)
8. [设备切换后自动刷新](#8-设备切换后自动刷新)
9. [完整实现示例](#9-完整实现示例)
10. [编辑器友好设计](#10-编辑器友好设计)
---
## 1. 系统总览
```
输入重映射职责:
├─ RebindPanel → 重映射面板(SettingsPanel 的子页签)
├─ RebindActionRow → 单条 Action 的绑定显示 + 重映射按钮
├─ ConflictDetector → 检测新绑定与现有绑定是否冲突
├─ KeyDisplayNameResolver → 将 InputControl.path 转换为可读字符串
└─ RebindPersistence → 将覆盖数据保存至 PlayerPrefs(JSON 格式)
```
**设计原则**:重映射仅修改 `InputActionAsset` 的运行时 Override,不改动资产本身;游戏启动时从 `PlayerPrefs` 读取并重新应用 Override。
---
## 2. 重映射架构
### 数据流
```
玩家点击 [重新绑定] 按钮
│
▼
InputActionRebindingExtensions
.PerformInteractiveRebinding(action, bindingIndex)
│
▼
等待玩家按键(屏蔽菜单键/退出键)
│
├─ 冲突检测 → 若冲突:弹出警告,允许覆盖或取消
│
▼
应用 Override
InputBinding.overridePath = newPath
│
▼
持久化:SaveOverrides()
│
▼
UI 刷新:所有 RebindActionRow 更新显示
```
### 核心 Unity API
| API | 用途 |
|-----|------|
| `action.PerformInteractiveRebinding(index)` | 启动交互式重映射 |
| `action.ApplyBindingOverride(index, path)` | 直接应用 Override |
| `action.RemoveBindingOverride(index)` | 移除某条 Override(恢复默认)|
| `asset.RemoveAllBindingOverrides()` | 全部恢复默认 |
| `asset.SaveBindingOverridesAsJson()` | 序列化所有 Override 为 JSON |
| `asset.LoadBindingOverridesFromJson(json)` | 从 JSON 恢复 Override |
---
## 3. RebindPanel — UI 面板
### 面板结构(UXML)
```
```
### RebindPanel.cs
```csharp
namespace BaseGames.Input
{
public class RebindPanel : MonoBehaviour
{
[SerializeField] InputReaderSO _inputReader;
[SerializeField] VisualTreeAsset _rowTemplate; // RebindActionRow.uxml
UIDocument _doc;
VisualElement _listeningOverlay;
Label _listeningLabel;
// 需要在面板中显示的 Action 名单(顺序即显示顺序)
static readonly string[] ShownActions =
{
"Move", "Jump", "Attack", "Parry", "Dash", "Heal", "Interact", "Pause"
};
void Awake()
{
_doc = GetComponent();
}
void OnEnable()
{
var root = _doc.rootVisualElement;
_listeningOverlay = root.Q("ListeningOverlay");
_listeningLabel = root.Q