chore: initial commit
This commit is contained in:
428
Docs/Design/42_DebugCheatSystem.md
Normal file
428
Docs/Design/42_DebugCheatSystem.md
Normal file
@@ -0,0 +1,428 @@
|
||||
# 42 · Debug / 开发者工具系统
|
||||
|
||||
> **命名空间** `BaseGames.Debug`(仅在 `UNITY_EDITOR || DEVELOPMENT_BUILD` 下编译)
|
||||
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
|
||||
> **依赖** `BaseGames.Player` · `BaseGames.Core`(GameManager · SaveManager)· `BaseGames.Progression`
|
||||
> **关联** 09_EditorExtensions(调试叠加层 §4)· 27_PerformanceBudgetGuide(性能监控)· 31_SaveDataSchema(SaveData 强制写入)
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [系统总览](#1-系统总览)
|
||||
2. [编译条件保护](#2-编译条件保护)
|
||||
3. [DebugConsole — 运行时控制台](#3-debugconsole--运行时控制台)
|
||||
4. [内置调试命令表](#4-内置调试命令表)
|
||||
5. [DebugConfigSO — 开发者配置资产](#5-debugconfigso--开发者配置资产)
|
||||
6. [快捷键绑定](#6-快捷键绑定)
|
||||
7. [无敌模式(GodMode)](#7-无敌模式godmode)
|
||||
8. [瞬移到房间(Teleport)](#8-瞬移到房间teleport)
|
||||
9. [强制解锁能力(UnlockAbility)](#9-强制解锁能力unlockability)
|
||||
10. [强制设置存档标志(SetFlag)](#10-强制设置存档标志setflag)
|
||||
11. [性能叠加层(PerfOverlay)](#11-性能叠加层perfoverlay)
|
||||
12. [QA 场景直接启动](#12-qa-场景直接启动)
|
||||
13. [编辑器菜单集成](#13-编辑器菜单集成)
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统总览
|
||||
|
||||
Debug 系统在**开发版 Build 和 Editor 中可用**,在 Release Build 中**完全剥离**,不影响性能和安全性。
|
||||
|
||||
```
|
||||
Debug 系统职责:
|
||||
├─ DebugConsole → 运行时命令控制台(类似 Quake/CS 控制台)
|
||||
├─ DebugConfigSO → 开发者专用配置(无敌、起始场景、默认能力等)
|
||||
├─ DebugOverlay → 屏幕角实时 FPS/GC/状态显示
|
||||
├─ CheatCommands → 命令实现集合(god / tp / unlock / setflag 等)
|
||||
└─ QABootstrapper → 支持任意场景直接播放时正确初始化 Persistent 层
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 编译条件保护
|
||||
|
||||
所有 Debug 代码用 `#if` 包裹,确保 Release 版本零开销:
|
||||
|
||||
```csharp
|
||||
// Assets/Scripts/Debug/DebugConsole.cs
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
|
||||
namespace BaseGames.Debug
|
||||
{
|
||||
public class DebugConsole : MonoBehaviour
|
||||
{
|
||||
// 实现见下方
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
### Player Settings 约定
|
||||
|
||||
| Build Type | `DEVELOPMENT_BUILD` | Debug 功能 |
|
||||
|-----------|-------------------|-----------|
|
||||
| Editor Play Mode | 自动为 true | ✅ 全功能 |
|
||||
| Development Build | 勾选 Development Build | ✅ 全功能 |
|
||||
| Release Build(QA 内部)| 不勾选 | ❌ 完全剥离 |
|
||||
| Release Build(发行版)| 不勾选 | ❌ 完全剥离 |
|
||||
|
||||
---
|
||||
|
||||
## 3. DebugConsole — 运行时控制台
|
||||
|
||||
### 激活方式
|
||||
|
||||
按 **` (反引号 / Tilde)** 键呼出/收起控制台覆盖层(UI Toolkit 实现,覆盖在 HUD 上方)。
|
||||
|
||||
### 控制台 UI 结构
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────┐
|
||||
│ [DEBUG CONSOLE] [×]关闭 │
|
||||
│ > god on ← 命令输入框 │
|
||||
│ ───────────────────────────────────────────────────── │
|
||||
│ [OK] GodMode 已开启 ← 输出日志区 │
|
||||
│ [OK] 传送至 Room_Cave_03 ← 最新在上 │
|
||||
│ [ERR] 未知命令: foo ← 错误用红色显示 │
|
||||
└───────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 命令解析
|
||||
|
||||
```csharp
|
||||
public class DebugConsole : MonoBehaviour
|
||||
{
|
||||
// 命令注册表:命令名 → handler
|
||||
private Dictionary<string, Action<string[]>> _commands = new();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// 内置命令自动注册
|
||||
Register("god", CheatCommands.GodMode);
|
||||
Register("tp", CheatCommands.Teleport);
|
||||
Register("unlock", CheatCommands.UnlockAbility);
|
||||
Register("setflag", CheatCommands.SetFlag);
|
||||
Register("givegeo", CheatCommands.GiveGeo);
|
||||
Register("killall", CheatCommands.KillAll);
|
||||
Register("timescale",CheatCommands.SetTimeScale);
|
||||
Register("perf", CheatCommands.TogglePerfOverlay);
|
||||
Register("reload", CheatCommands.ReloadScene);
|
||||
Register("help", CheatCommands.Help);
|
||||
}
|
||||
|
||||
public void Register(string name, Action<string[]> handler)
|
||||
=> _commands[name.ToLower()] = handler;
|
||||
|
||||
void Execute(string input)
|
||||
{
|
||||
var parts = input.Trim().Split(' ');
|
||||
if (parts.Length == 0) return;
|
||||
var cmd = parts[0].ToLower();
|
||||
var args = parts[1..];
|
||||
|
||||
if (_commands.TryGetValue(cmd, out var handler))
|
||||
handler(args);
|
||||
else
|
||||
LogError($"未知命令: {cmd}(输入 help 查看命令列表)");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 内置调试命令表
|
||||
|
||||
| 命令 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| `god` | `god [on\|off]` | 切换无敌模式(无参数 = toggle)|
|
||||
| `tp` | `tp <SceneName> [SpawnId]` | 瞬移到指定场景和出生点 |
|
||||
| `unlock` | `unlock <AbilityName>` | 解锁指定能力(如 `unlock doublejump`)|
|
||||
| `unlock all` | `unlock all` | 解锁全部能力 |
|
||||
| `setflag` | `setflag <flagKey> <true\|false>` | 强制写入 SaveData 任意布尔标志 |
|
||||
| `givegeo` | `givegeo <amount>` | 给予指定数量货币 |
|
||||
| `givehp` | `givehp <amount>` | 设置当前 HP |
|
||||
| `killall` | `killall` | 击杀当前房间所有敌人 |
|
||||
| `spawnenemy` | `spawnenemy <EnemyId> [x y]` | 在指定位置生成敌人 |
|
||||
| `timescale` | `timescale <0.0–2.0>` | 设置 `Time.timeScale` |
|
||||
| `perf` | `perf [on\|off]` | 切换性能叠加层显示 |
|
||||
| `reload` | `reload` | 重新加载当前场景 |
|
||||
| `savestate` | `savestate` | 立即写入存档 |
|
||||
| `loadstate` | `loadstate` | 重新读取最新存档 |
|
||||
| `form` | `form <FormId>` | 强制切换玩家形态(如 `form EarthSoul`)|
|
||||
| `help` | `help [cmd]` | 显示命令列表或指定命令帮助 |
|
||||
|
||||
---
|
||||
|
||||
## 5. DebugConfigSO — 开发者配置资产
|
||||
|
||||
```csharp
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
[CreateAssetMenu(menuName = "Debug/DebugConfig")]
|
||||
public class DebugConfigSO : ScriptableObject
|
||||
{
|
||||
[Header("启动配置")]
|
||||
public string startSceneOverride; // 非空时 QABootstrapper 加载此场景
|
||||
public string startSpawnPointId; // 配合 startSceneOverride
|
||||
|
||||
[Header("开局默认能力(跳过前期解锁,快速测试后期内容)")]
|
||||
public bool startWithDoubleJump;
|
||||
public bool startWithWallGrab;
|
||||
public bool startWithDash;
|
||||
public bool startWithSwim;
|
||||
public string startFormId = "Form_HeavenSoul";
|
||||
|
||||
[Header("开局资源")]
|
||||
public int startGeo = 0;
|
||||
public int startMaxHP = 5;
|
||||
|
||||
[Header("自动化测试")]
|
||||
public bool enableGodModeAtStart;
|
||||
public bool skipCutscenes; // 所有 Timeline 直接跳过
|
||||
public bool skipDialogue; // 所有对话直接结束
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
`DebugConfigSO` 资产放在 `Assets/Debug/`(不加入 Addressable Group),不进入发行包。
|
||||
|
||||
---
|
||||
|
||||
## 6. 快捷键绑定
|
||||
|
||||
快捷键仅在 `UNITY_EDITOR || DEVELOPMENT_BUILD` 下注册,不占用玩家输入:
|
||||
|
||||
| 快捷键 | 功能 |
|
||||
|--------|------|
|
||||
| **`` ` ``** (Tilde) | 呼出/收起控制台 |
|
||||
| **F1** | 切换无敌模式 |
|
||||
| **F2** | 切换 PerfOverlay |
|
||||
| **F3** | 快速存档 |
|
||||
| **F4** | 快速读档(回到最后存档点)|
|
||||
| **F5** | 重新加载当前场景 |
|
||||
| **F8** | 击杀当前房间所有敌人 |
|
||||
| **Ctrl + Shift + T** | 打开 Teleport 快捷面板 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 无敌模式(GodMode)
|
||||
|
||||
```csharp
|
||||
public static class CheatCommands
|
||||
{
|
||||
public static void GodMode(string[] args)
|
||||
{
|
||||
bool enable = args.Length == 0
|
||||
? !PlayerStats.Instance.IsGodMode
|
||||
: args[0] == "on";
|
||||
|
||||
PlayerStats.Instance.IsGodMode = enable;
|
||||
DebugConsole.Log($"GodMode {(enable ? "开启" : "关闭")}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`PlayerStats.IsGodMode` 属性在 `HurtBox.ReceiveDamage` 入口处检测:
|
||||
|
||||
```csharp
|
||||
// HurtBox.cs
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
if (_owner.TryGetComponent<PlayerStats>(out var ps) && ps.IsGodMode)
|
||||
return; // 跳过伤害处理
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 瞬移到房间(Teleport)
|
||||
|
||||
```csharp
|
||||
public static async void Teleport(string[] args)
|
||||
{
|
||||
if (args.Length == 0) { DebugConsole.LogError("用法: tp <SceneName> [SpawnId]"); return; }
|
||||
|
||||
string sceneName = args[0];
|
||||
string spawnId = args.Length > 1 ? args[1] : "Default";
|
||||
|
||||
// 复用正常的场景加载流程,保证 GameManager / SaveManager 状态一致
|
||||
var channel = Resources.Load<LoadSceneEventChannelSO>("Events/LoadSceneChannel");
|
||||
channel.Raise(new LoadSceneEvent(sceneName, spawnId));
|
||||
DebugConsole.Log($"传送至 {sceneName} @ {spawnId}");
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:Teleport 不影响 SaveData,不触发正常 OnRoomEntered 流程,仅用于测试。
|
||||
|
||||
### Teleport 快捷面板
|
||||
|
||||
`Ctrl + Shift + T` 打开一个 EditorWindow 风格的浮层(Runtime UI Toolkit),列出所有已知场景名(从 `ProgressionSystem.RegionDefinitionSO` 读取),点击即瞬移。
|
||||
|
||||
---
|
||||
|
||||
## 9. 强制解锁能力(UnlockAbility)
|
||||
|
||||
```csharp
|
||||
public static void UnlockAbility(string[] args)
|
||||
{
|
||||
if (args.Length == 0) { DebugConsole.LogError("用法: unlock <ability|all>"); return; }
|
||||
|
||||
if (args[0] == "all")
|
||||
{
|
||||
foreach (AbilityType a in Enum.GetValues(typeof(AbilityType)))
|
||||
PlayerStats.Instance.UnlockAbility(a);
|
||||
DebugConsole.Log("已解锁全部能力");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<AbilityType>(args[0], true, out var ability))
|
||||
{
|
||||
PlayerStats.Instance.UnlockAbility(ability);
|
||||
DebugConsole.Log($"已解锁: {ability}");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.LogError($"未知能力: {args[0]}");
|
||||
DebugConsole.Log("可用值: " + string.Join(", ", Enum.GetNames(typeof(AbilityType))));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 强制设置存档标志(SetFlag)
|
||||
|
||||
用于测试"Boss 已击败 → 大门已开"等状态敏感逻辑:
|
||||
|
||||
```csharp
|
||||
public static void SetFlag(string[] args)
|
||||
{
|
||||
// 语法: setflag world.defeatedBossIds Boss_Forest true
|
||||
if (args.Length < 2) { DebugConsole.LogError("用法: setflag <path> <value>"); return; }
|
||||
|
||||
// 通过反射或 switch 路由到 SaveData 对应字段
|
||||
SaveManager.Instance.SetDebugFlag(args[0], args[1]);
|
||||
DebugConsole.Log($"标志 [{args[0]}] 已设为 {args[1]}");
|
||||
}
|
||||
```
|
||||
|
||||
`SaveManager.SetDebugFlag` 内部通过 `switch` 路由已知键路径(不做通用反射,避免安全风险):
|
||||
|
||||
```csharp
|
||||
public void SetDebugFlag(string path, string value)
|
||||
{
|
||||
switch (path)
|
||||
{
|
||||
case "world.defeatedBossIds.add":
|
||||
Current.World.DefeatedBossIds.Add(value); break;
|
||||
case "player.abilities.swim":
|
||||
Current.Player.Abilities.Swim = bool.Parse(value); break;
|
||||
case "player.abilities.dash":
|
||||
Current.Player.Abilities.Dash = bool.Parse(value); break;
|
||||
// ... 其他已知路径
|
||||
default:
|
||||
UnityEngine.Debug.LogWarning($"[Debug] 不支持的标志路径: {path}"); break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 性能叠加层(PerfOverlay)
|
||||
|
||||
`PerfOverlay` 在屏幕右上角显示实时诊断数据:
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ FPS: 62 (16.1ms) │
|
||||
│ GC: 0.2 KB/frame │
|
||||
│ Draw Calls: 128 │
|
||||
│ Particles: 34 / 500 │
|
||||
│ ─────────────────────── │
|
||||
│ PlayerState: WallGrab │
|
||||
│ HP: 4/5 Soul: 66/99 │
|
||||
│ Form: HeavenSoul │
|
||||
│ Pos: (-12.5, 3.0) │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
实现基于 Unity `FrameTimingManager` + `GarbageCollector.GetTotalMemory(false)`,性能采样间隔 0.25 秒,不产生额外 GC。
|
||||
|
||||
参见 09_EditorExtensions §4(调试叠加层)获取完整实现细节。
|
||||
|
||||
---
|
||||
|
||||
## 12. QA 场景直接启动
|
||||
|
||||
Unity Editor 中直接播放非 Persistent 场景时(如 `Room_Cave_03`),`QABootstrapper` 自动初始化必要系统:
|
||||
|
||||
```csharp
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
/// <summary>
|
||||
/// 当非 Persistent 场景被直接播放时,自动加载 Persistent 场景并初始化调试配置。
|
||||
/// 挂在每个 Room_* 场景的 [Debug] 根物件下(Editor Only)。
|
||||
/// </summary>
|
||||
public class QABootstrapper : MonoBehaviour
|
||||
{
|
||||
[SerializeField] DebugConfigSO _config;
|
||||
|
||||
async void Awake()
|
||||
{
|
||||
// 检查 Persistent 场景是否已加载
|
||||
if (!SceneManager.GetSceneByName("Persistent").isLoaded)
|
||||
{
|
||||
await Addressables.LoadSceneAsync(
|
||||
AddressKeys.ScenePersistent, LoadSceneMode.Additive).Task;
|
||||
}
|
||||
|
||||
// 应用 DebugConfig 起始状态
|
||||
if (_config != null)
|
||||
ApplyDebugConfig(_config);
|
||||
}
|
||||
|
||||
void ApplyDebugConfig(DebugConfigSO cfg)
|
||||
{
|
||||
var stats = PlayerStats.Instance;
|
||||
if (cfg.startWithDoubleJump) stats.UnlockAbility(AbilityType.DoubleJump);
|
||||
if (cfg.startWithWallGrab) stats.UnlockAbility(AbilityType.WallGrab);
|
||||
if (cfg.startWithDash) stats.UnlockAbility(AbilityType.Dash);
|
||||
if (cfg.startWithSwim) stats.UnlockAbility(AbilityType.Swim);
|
||||
if (cfg.enableGodModeAtStart) stats.IsGodMode = true;
|
||||
|
||||
var form = FormController.Instance;
|
||||
form.SwitchForm(cfg.startFormId);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 编辑器菜单集成
|
||||
|
||||
```
|
||||
菜单: BaseGames/Debug/
|
||||
├── [开启 GodMode] → PlayerPrefs 写入临时标志,下次 Play 自动开启
|
||||
├── [快速跳转场景...] → 打开 SceneSelector EditorWindow
|
||||
├── [解锁所有能力] → 修改 DebugConfigSO 默认值
|
||||
├── [清空存档(Slot 0)] → 删除 slot_0.json(确认弹窗)
|
||||
├── [打开 SaveData 编辑器] → 打开 SaveDataEditor 窗口(见 31_SaveDataSchema §10)
|
||||
└── [生成 QABootstrapper 到当前场景] → 自动在当前场景添加 QABootstrapper 组件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:常见 QA 测试流程
|
||||
|
||||
| 测试目标 | 推荐命令序列 |
|
||||
|---------|------------|
|
||||
| 测试 Boss 关卡(跳过前期)| `unlock all` → `tp Boss_Forest` |
|
||||
| 测试后期区域(深渊)| `unlock all` → `tp Room_Abyss_01` |
|
||||
| 复现特定死亡存档状态 | `setflag world.defeatedBossIds.add Boss_Forest` → `savestate` → 手动触发死亡 |
|
||||
| 性能 Profile 特效密集场景 | `perf on` → `spawnenemy EnemySwarmType 0 0` × 20 |
|
||||
| 测试游泳系统(无能力)| `tp Room_Abyss_Lake_01` → 进入水体观察溺水计时 |
|
||||
| 测试游泳系统(有能力)| `unlock swim` → `tp Room_Abyss_Lake_01` |
|
||||
|
||||
---
|
||||
|
||||
*文档版本 1.0 · 2025*
|
||||
Reference in New Issue
Block a user