# 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> _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 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 [SpawnId]` | 瞬移到指定场景和出生点 | | `unlock` | `unlock ` | 解锁指定能力(如 `unlock doublejump`)| | `unlock all` | `unlock all` | 解锁全部能力 | | `setflag` | `setflag ` | 强制写入 SaveData 任意布尔标志 | | `givegeo` | `givegeo ` | 给予指定数量货币 | | `givehp` | `givehp ` | 设置当前 HP | | `killall` | `killall` | 击杀当前房间所有敌人 | | `spawnenemy` | `spawnenemy [x y]` | 在指定位置生成敌人 | | `timescale` | `timescale <0.0–2.0>` | 设置 `Time.timeScale` | | `perf` | `perf [on\|off]` | 切换性能叠加层显示 | | `reload` | `reload` | 重新加载当前场景 | | `savestate` | `savestate` | 立即写入存档 | | `loadstate` | `loadstate` | 重新读取最新存档 | | `form` | `form ` | 强制切换玩家形态(如 `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(out var ps) && ps.IsGodMode) return; // 跳过伤害处理 #endif ``` --- ## 8. 瞬移到房间(Teleport) ```csharp public static async void Teleport(string[] args) { if (args.Length == 0) { DebugConsole.LogError("用法: tp [SpawnId]"); return; } string sceneName = args[0]; string spawnId = args.Length > 1 ? args[1] : "Default"; // 复用正常的场景加载流程,保证 GameManager / SaveManager 状态一致 var channel = Resources.Load("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 "); return; } if (args[0] == "all") { foreach (AbilityType a in Enum.GetValues(typeof(AbilityType))) PlayerStats.Instance.UnlockAbility(a); DebugConsole.Log("已解锁全部能力"); return; } if (Enum.TryParse(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 "); 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 /// /// 当非 Persistent 场景被直接播放时,自动加载 Persistent 场景并初始化调试配置。 /// 挂在每个 Room_* 场景的 [Debug] 根物件下(Editor Only)。 /// 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*