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

29 KiB
Raw Permalink Blame History

09 · 编辑器扩展

命名空间 BaseGames.Editor
所属文档集 ← 返回索引 · 总览
程序集 BaseGames.Editor.asmdefEditor-only不打包进 Runtime


目录

  1. 设计原则
  2. 自定义 Inspector 列表
  3. EditorWindow 工具列表
  4. Scene 视图 Gizmos 汇总
  5. Play Mode 调试叠加层
  6. ContextMenu 工具
  7. 编辑器菜单结构
  8. 自动化检查工具
  9. 高级工具集(补充)

1. 设计原则

  • 编辑器扩展与运行时代码完全分离:所有编辑器扩展代码置于 Editor/ 文件夹,使用独立 .asmdef
  • 工具服务于制作流程:每个工具针对具体的制作痛点,不做泛用性过度设计
  • 视觉化优先Gizmos 和自定义 Inspector 的目标是"看一眼就知道配置是否正确"
  • 不破坏运行时行为:所有 Inspector 修改通过 SerializedObject.ApplyModifiedProperties() 标准流程支持撤销Undo
  • UI 技术选型:所有编辑器扩展统一使用 Unity UI ToolkitUIElements,不使用 IMGUIOnGUI / EditorGUILayout

UI Toolkit 统一规范

自定义 Inspector — 使用 CreateInspectorGUI() + BindProperty()

[CustomEditor(typeof(PlayerController))]
public class PlayerControllerEditor : Editor
{
    public override VisualElement CreateInspectorGUI()
    {
        var root = new VisualElement();

        // 默认字段绑定(替代 DrawDefaultInspector
        InspectorElement.FillDefaultInspector(root, serializedObject, this);

        // 自定义运行时状态区
        var statusFoldout = new Foldout { text = "运行时状态Play Mode" };
        var stateLabel    = new Label();
        stateLabel.schedule.Execute(() =>
        {
            if (!EditorApplication.isPlaying) return;
            var t = target as PlayerController;
            stateLabel.text = $"State: {t.CurrentStateName}  HP: {t.HP}/{t.MaxHP}";
        }).Every(100);  // 100ms 轮询
        statusFoldout.Add(stateLabel);

        // 调试按钮Play Mode only
        var debugGroup = new VisualElement();
        debugGroup.SetEnabled(false);
        EditorApplication.playModeStateChanged += state =>
            debugGroup.SetEnabled(state == PlayModeStateChange.EnteredPlayMode);
        var hurtBtn = new Button(() => (target as PlayerController)?.TakeDamage(2)) { text = "Hurt 2" };
        debugGroup.Add(hurtBtn);

        root.Add(statusFoldout);
        root.Add(debugGroup);
        return root;
    }
}

EditorWindow — 使用 CreateGUI() + 内置控件:

public class MyTool : EditorWindow
{
    [MenuItem("BaseGames/My Tool")]
    public static void Open() => GetWindow<MyTool>("My Tool");

    public void CreateGUI()
    {
        var splitView = new TwoPaneSplitView(0, 260f, TwoPaneSplitViewOrientation.Horizontal);
        var left  = new ScrollView();
        var right = new VisualElement();
        splitView.Add(left);
        splitView.Add(right);
        rootVisualElement.Add(splitView);
    }
}

§2§9 各工具使用的关键 UI Toolkit 控件

工具 关键控件 说明
§2.x 自定义 Inspector ProgressBarFoldoutButtonLabel CreateInspectorGUI() 替代 OnInspectorGUI()
§2.3 ParrySystem时间轴 自绘 VisualElementgenerateVisualContent + Painter2D 替代 GUI.Box 手绘条
§3.13.4 简单 EditorWindow ListViewButtonTextField CreateGUI() 替代 OnGUI()
§9.1 BT 验证器 TreeView 原生树形控件
§9.2 进程流程图 GraphViewUnityEditor.Experimental.GraphView 节点图;见注意事项
§9.3 GameState 分析器 MultiColumnListView 矩阵视图
§9.4 伤害模拟器 TwoPaneSplitView + 自绘直方图(Painter2D 参数面板 + 分布图
§9.7 装备预览 TwoPaneSplitView + DropdownField 响应式分栏
§9.8 构建验证器 MultiColumnListView + 可点击 Label 多列结果 + 跳转

GraphView 注意UnityEditor.Experimental.GraphView 标注为 ExperimentalAPI 可能在 Unity 版本间变动。如遇稳定性问题9.2 进程流程图可改为 VisualElement 手绘节点(generateVisualContent + Painter2D.DrawBezierCurve),无需依赖 Experimental API。

USS 样式文件存放Assets/Editor/UI/USS/ — 统一样式变量(颜色、间距、字体大小),通过 styleSheets.Add() 挂载。


2. 自定义 Inspector 列表

2.1 PlayerController Inspector

目标Play Mode 下直观监控玩家状态,快速触发测试。

布局规划

┌─ PlayerController ─────────────────────────────────┐
│  ◈ 状态信息                                         │
│  ┌──────────────────────────────────────────────┐  │
│  │ Current State  RunState                      │  │
│  │ HP             ████████████░░  8 / 10        │  │
│  │ Soul           ███████░░░░░░░  66 / 99       │  │
│  │ Geo            340                           │  │
│  │ IsGrounded     ✓   FacingDir  → (+1)         │  │
│  │ Invincible     ░░░░░░░░░░ (0.00s remaining)  │  │
│  └──────────────────────────────────────────────┘  │
│  ◈ 已解锁能力                                       │
│  ┌──────────────────────────────────────────────┐  │
│  │ [✓ Parry] [✓ DoubleJump] [✗ WallJump]       │  │
│  │ [✗ AerialDash] [✗ Swim]                      │  │
│  └──────────────────────────────────────────────┘  │
│  ◈ 调试工具Play Mode Only                       │
│  [Force Idle] [Force Run] [Force Air]              │
│  [Deal Damage: 2] [Deal Damage: 10] [Instant Kill] │
│  [Add Soul: 33] [Add Geo: 100]                     │
│  [Unlock All Abilities] [Lock All Abilities]       │
└────────────────────────────────────────────────────┘

实现方式:继承 Editor,重写 CreateInspectorGUI() 返回 VisualElement 树。进度条使用 UI Toolkit ProgressBar 控件Play Mode 限定区域通过 debugGroup.SetEnabled(EditorApplication.isPlaying) + EditorApplication.playModeStateChanged 回调控制显隐。


2.2 EnemyBase Inspector

目标:快速查看敌人 AI 状态,测试战斗参数。

┌─ EnemyBase ──────────────────────────────────────┐
│  ◈ 运行时状态Play Mode                        │
│  State: Controlled  HP: █████████░  90/100       │
│  BD Node: BD_MoveToPlayer (Running)              │
│  Nav Status: Moving → (12.5, -3.0) [0.8m left]  │
│  ◈ 属性配置                                      │
│  Stats SO: [ES_GruntWarrior ▼]                   │
│  AnimCfg : [EA_GruntWarrior ▼]                   │
│  ◈ 调试工具Play Mode Only                    │
│  [Force Stagger] [Force Death] [Reset HP]        │
│  [Disable BD] [Enable BD] [Reload BD Tree]       │
└──────────────────────────────────────────────────┘

2.3 ParrySystem Inspector

目标:可视化弹反时间轴,测试弹反参数。

┌─ ParrySystem ────────────────────────────────────┐
│  ◈ 时间轴预览                                    │
│  [▌ Startup ▌▌▌▌▌▌▌ Active Window ▌▌ Endlag ▌] │
│     0.05s              0.28s           0.10s     │
│  ◈ 运行时状态Play Mode                       │
│  State: Active  Timer: 0.19s / 0.28s            │
│  Window:   ████████████░░░  68%                 │
│  CounterW: ──────────────  (Inactive)            │
│  ◈ 调试工具                                     │
│  [Trigger Parry Success] [Trigger Parry Fail]   │
│  [Open Counter Window]   [Reset]                 │
└──────────────────────────────────────────────────┘

时间轴预览 在 Edit Mode 下也可显示(根据 ParryConfigSO 参数计算比例),方便策划调整数值时直观预览窗口比例。


2.4 RoomCameraBounds Inspector

目标:防止镜头边界配置错误。

┌─ RoomCameraBounds ───────────────────────────────┐
│  ◈ 边界检查                                      │
│  Bounds Size: (32.0, 18.0)                       │
│  Camera View (4.22 OrthoSize): (29.9, 16.8)     │
│  Status: ✓ 边界大于镜头视野                       │
│  ─────────────────────────────────────────────── │
│  [Preview Camera View in Scene]                  │
│  [自动调整 PolygonCollider2D 至最小安全尺寸]      │
└──────────────────────────────────────────────────┘

2.5 DamageSourceSO Inspector

目标:策划配置攻击参数时,即时预览伤害计算和属性。

┌─ DamageSourceSO ─────────────────────────────────┐
│  ◈ 伤害预览                                      │
│  BaseDamage × Multiplier = Final                 │
│       5     ×    1.0     =   5                  │
│  ◈ 击退预览                                      │
│  KnockbackForce: 8.0  Direction: →               │
│  HitStun: 0.30s                                  │
│  ◈ 属性标记                                      │
│  [CanBeParried ✓] [Unblockable ✗] [IgnoreIFrame ✗]│
│  ◈ 伤害类型                                      │
│  Type: Normal (物理)                             │
│  HitFxType: Slash                                │
└──────────────────────────────────────────────────┘

3. EditorWindow 工具列表

3.1 房间连接验证工具

菜单路径BaseGames > World > Room Connection Validator

功能

┌─ Room Connection Validator ──────────────────────────────────────┐
│  扫描所有 Room_*.unity 场景中的 RoomTransition 组件              │
│                                                                  │
│  房间                     出口ID           目标场景             │
│  ──────────────────────────────────────────────────────────────  │
│  Room_Forest_01       Door_To_Forest_02  ✓ Room_Forest_02 存在  │
│  Room_Forest_02       Door_To_Cave_01    ✓ Room_Cave_01 存在    │
│  Room_Cave_01         Door_To_Boss       ✗ Boss_Cave 不存在 ⚠  │
│                                                                  │
│  SpawnPoint 检查:                                                │
│  Room_Forest_02 缺少 SpawnPoint "SP_From_Forest_01_Door" ⚠     │
│                                                                  │
│  [重新扫描]   [选中有错误的场景]   [导出验证报告.md]             │
└──────────────────────────────────────────────────────────────────┘

3.2 SO 事件频道监视器

菜单路径BaseGames > Core > Event Channel Monitor

功能Play Mode 下实时监听所有 SO 事件频道的触发情况。

┌─ Event Channel Monitor ──────────────────────────────────────────┐
│  [▶ Play Mode Only]   [清除日志]   [暂停记录]                    │
│                                                                  │
│  时间      事件频道               数据                          │
│  ──────────────────────────────────────────────────────────────  │
│  0.012s    OnPlayerHPChanged      value: 8                      │
│  0.013s    OnHitConfirmed         DMG:5, Knockback:8.0          │
│  1.245s    OnParrySuccess         DMG:5, Flags: CanBeParried    │
│  1.248s    OnPlayerHPChanged      value: 8 (no change)          │
│  2.001s    OnRoomEntered          Transform: Room_Forest_01     │
│                                                                  │
│  [筛选频道: ___________]  [仅显示Player]  [仅显示Combat]        │
└──────────────────────────────────────────────────────────────────┘

3.3 NavSurface 快速烘焙工具

菜单路径BaseGames > Navigation > Quick Bake All Rooms

一键遍历所有场景中的 NavSurface,按顺序烘焙 PathBerserker2d 导航网格并保存场景:

┌─ NavSurface Bake Tool ──────────────────────────────────────────┐
│  扫描到 NavSurface 数量: 12                                     │
│                                                                  │
│  Room_Forest_01: ✓ 已烘焙 (2024-01-01 12:00)                   │
│  Room_Forest_02: ⚠ 需要重新烘焙(场景已修改)                   │
│  Room_Cave_01:   ✓ 已烘焙                                      │
│                                                                  │
│  [烘焙全部]  [仅烘焙已修改]  [验证所有路径连通性]               │
└─────────────────────────────────────────────────────────────────┘

3.4 SaveData 查看器

菜单路径BaseGames > World > SaveData Viewer

┌─ SaveData Viewer ───────────────────────────────────────────────┐
│  Slot 0  |  Slot 1  |  Slot 2                                  │
│  ─────────────────────────────────────────────────────────────  │
│  Player: HP 5/5 | Geo 340 | Soul 66/99                        │
│  Scene: Room_Forest_01 @ SP_Forest_01_Entry                    │
│  Abilities: Parry✓ DoubleJump✓ WallJump✗ Dash✗ Swim✗         │
│  Discovered: 3 rooms | Cleared: 1 | Bosses Defeated: 0        │
│  ─────────────────────────────────────────────────────────────  │
│  [JSON 原始视图]  [编辑并保存]  [删除此存档]                    │
└─────────────────────────────────────────────────────────────────┘

4. Scene 视图 Gizmos 汇总

组件 Gizmo 形状 颜色 说明
HitBox BoxWire 橙色激活实心50%透明) 攻击判定区域
HurtBox BoxWire 绿色(受击中:红色闪烁) 受击区域
SpawnPoint 旗帜Icon + 文字 绿色 玩家出生点标识
RoomTransition 箭头 → 目标场景文字 蓝色 房间出口方向
RoomCameraBounds Polygon + 内层相机视野矩形 青色 镜头约束范围
EnemyBase 检测范围 圆圈 黄色 玩家检测半径
EnemyBase 攻击范围 圆圈 红色(实线) 攻击触发范围
EnemyBase 巡逻路径 线段 + 端点 蓝色虚线 巡逻 A~B 路径
EnemyBase 寻路目标 箭头 绿色 当前导航目标
EnemyBase 视线检测 射线 青色(遮挡:红色) Raycast 视线
NavLink 弧形箭头 紫色 导航跳跃链接
HazardZone BoxWire 红色实心30%透明) 危险区域范围
AbilityUnlock 星形Icon 金色 能力解锁物件标识
SavePoint 旗帜Icon已激活/未激活) 蓝色/灰色 存档点状态
ParrySystem(窗口中) 圆圈动画 黄色 弹反窗口激活指示

5. Play Mode 调试叠加层

DebugOverlayWindow:按 F1 键(仅 Development Build 和 Editor 中)显示调试叠加层。

叠加层布局

┌─────────────────────────────────────────────────────────────────┐
│  [F1 关闭]                                   FPS: 144          │
│                                                                  │
│  Player State: RunState       HP: 8/10  Soul: 66  Geo: 340     │
│  IsGrounded: ✓   Facing: →   Invincible: ✗                     │
│                                                                  │
│  Active Camera: VCam_Explore  Blend: Complete                  │
│  Current Room: Room_Forest_01                                   │
│                                                                  │
│  Enemies in Scene: 2   Alive: 1   Dead: 1                      │
│                                                                  │
│  ─────────── 快捷键 ───────────────────────────────────────     │
│  F2 切换无敌  F3 加满 Soul  F4 下一检查点  F5 重新加载场景      │
│  F6 切换 Gizmos  F7 切换碰撞体显示  F8 切换 AI 暂停            │
└─────────────────────────────────────────────────────────────────┘

6. ContextMenu 工具

通过 Inspector 右键菜单([ContextMenu])调用,无需进入 Play Mode

组件 右键菜单项 说明
EnemyStatsSO Print Stats Summary Console 打印该敌人完整属性表
DamageSourceSO Calculate DPS Console 打印理论 DPSBaseDamage / AttackCooldown
RoomCameraBounds Fit to Room Tilemap 自动调整 PolygonCollider2D 以包裹 Tilemap 边界
NavSurface Bake NavMesh 立即烘焙此 NavSurface
SavePoint Mark As Activated 设置 SavePoint 为已激活状态(用于测试继续游戏场景)
RoomTransition Validate Target Scene 检查目标场景是否存在于 BuildSettings

7. 编辑器菜单结构

Unity 菜单栏 BaseGames/ 下的所有工具入口:

BaseGames/
├── Core/
│   └── Event Channel Monitor          → EventChannelMonitorWindow
│
├── World/
│   ├── Room Connection Validator      → RoomConnectionValidatorWindow
│   ├── SaveData Viewer                → SaveDataViewerWindow
│   └── Rebuild All Spawn Points       → 扫描并重新生成 SpawnPoint ID 索引
│
├── Navigation/
│   ├── Quick Bake All Rooms           → NavSurfaceBakeTool
│   └── Validate Path Connectivity     → 检查所有房间 NavLink 连通性
│
├── Combat/
│   └── DPS Calculator                 → 输入 DamageSourceSO + AttackCooldown输出 DPS 表格
│
└── Settings/
    ├── Open Project Layer Matrix      → 快速跳转到 ProjectSettings > Physics2D Layer Matrix
    └── Validate Assembly Definitions  → 检查 .asmdef 依赖关系是否符合零耦合原则

8. 自动化检查工具

场景验证检查(保存场景时自动运行)

SceneValidationProcessor(继承 AssetModificationProcessor),在保存场景时自动检查:

检查项 错误级别 说明
场景是否有 RoomCameraBounds Warning 遗漏镜头约束
场景是否有 NavSurface Warning 有敌人的场景必须有 NavSurface
所有 RoomTransition 目标场景是否在 BuildSettings Error 目标场景不存在
所有 HitBox 是否有对应 DamageSourceSO Error 攻击参数未配置
所有 EnemyBase 是否有 EnemyStatsSO Error 敌人属性未配置

检查结果在 Console 窗口输出,并在 Scene Validation 浮窗中汇总。

Assembly Definition 依赖校验

BaseGames > Settings > Validate Assembly Definitions 检查 .asmdef 是否遵循零耦合原则(如 BaseGames.Player 不应直接依赖 BaseGames.Enemies),输出依赖图 Mermaid 格式并在 Console 报警。


9. 高级工具集(补充)

9.1 Behavior Tree 验证器

Tools > Zeling > BT ValidatorBaseGames.Editor.BehaviorTreeValidator

扫描 Assets/ 下所有 Behavior Designer .asset 文件,检测:

检查项 级别 说明
孤立节点(无父节点且非根) Warning 表示悬空的设计草稿节点
缺失 TaskName 的 Action/Condition Error 会导致 BD 无法编译
SharedVariable 引用为空 Warning 运行时会 NullRef
Action 节点最大深度 > 12 Warning 过深 BT 建议拆分子树
同一 Agent 上同时存在多个 BehaviorTree 组件 Error 会互相覆盖

输出到 Console双击跳转到对应 BD 资产。

9.2 进程流程图Progression Flow Graph

Tools > Zeling > Progression Flow GraphEditorWindow

┌─ Progression Flow Graph ─────────────────────────────────────┐
│  [刷新]   [导出为 PNG]   [高亮未实现节点]                     │
│                                                               │
│  ┌Forest──────┐  ─击败SpiderGuard→  ┌Cave───────────────┐   │
│  │SP_Forest_01│                     │SP_Cave_01         │   │
│  │HP+2 Heart  │                     │WallJump Unlock    │   │
│  └────────────┘                     └───────────────────┘   │
│          │                                    │              │
│          └──────── 需要 Dash ─────────────────┘              │
└──────────────────────────────────────────────────────────────┘
  • 自动读取所有 ProgressLock/AbilityGate SO 及 BossProgressTracker 配置
  • 用 Bezier 曲线绘制依赖关系(红色=未解锁,绿色=已解锁)
  • 右键节点 → "模拟解锁" → 在 Editor 中测试后续节点是否变绿

9.3 GameState 转换分析器

Tools > Zeling > GameState AnalyzerEditorWindow

  • 列出 GameManager 中所有 GameState 枚举值
  • 显示每个状态下:允许的输入 Action Map、哪些 SO 频道被监听
  • 矩阵视图:行 = 事件频道,列 = 当前状态;标注"会触发"/"被忽略"
  • 辅助排查"暂停状态下攻击事件仍被响应"等逻辑漏洞

9.4 伤害模拟器Damage Simulator

DamageSourceSO 右键菜单 → Simulate Damage,弹出快速面板:

┌─ Damage Simulator: DamageSource_PlayerAttack1 ──┐
│  目标防御:    0  [ ] 格  受伤类型: Normal       │
│  CharmBonus: × 1.5  + 12剑尖魅力           │
│  弱点:        × 1.0  [✓] 骑士弱点(十字架)    │
│  ─────────────────────────────────────────────  │
│  最终伤害:   14基础 9 × 1.5 + 1 向上取整)   │
│  [运行 1000 次随机伤害分布图]                   │
└─────────────────────────────────────────────────┘

不需要进入 Play Mode 即可快速验证伤害数值设计。

9.5 地图贴图验证器Map Texture Validator

Tools > Zeling > Map Texture Validator

  • 读取 MapRoomDataSO.roomOutlineTexture 与对应场景的实际 Tilemap 边界
  • 对比边界框是否对齐(允许 ± 1 tile 误差)
  • 检测 Texture 是否以 Read/Write 导入SetPixels 需要)
  • 批量输出不匹配的房间列表 + 截图预览

9.6 音频导入预处理器Audio Import Preprocessor

BaseGames.Editor.AudioImportPreprocessor(继承 AssetPostprocessor

规则表(AudioImportRulesSO,可在 Inspector 配置):

文件名前缀 平台 采样率 压缩格式 Load Type
BGM_ All 44100 Vorbisq=0.4 Streaming
SFX_ All 22050 ADPCM CompressedInMemory
AMB_ All 22050 Vorbisq=0.3 Streaming
Voice_ All 22050 Vorbisq=0.6 CompressedInMemory

导入音频时自动按文件名前缀应用对应设置Console 输出 [AudioPreprocessor] Applied: SFX rule → SFX_Player_Hurt.wav

9.7 装备预览工具Equip Preview

Tools > Zeling > Equip PreviewEditorWindow

┌─ Equip Preview ──────────────────────────────────────────────┐
│  选择魅力组合(最多 4 个槽位):                               │
│  槽1: [剑之力量魅力  ▼]  槽2: [灵魂强化魅力  ▼]              │
│  槽3: [无             ]  槽4: [无             ]              │
│  ─────────────────────────────────────────────────────────  │
│  最终属性:                                                    │
│  攻击力:  9 × 1.5 = 13   法术消耗: 33 × 0.67 = 22           │
│  弹反 Soul: 10 → 10       法术弹数: 3 → 3                   │
│  路径成本: 4 个通知EquipmentManager.Apply × 4            │
│  [复制配置 JSON]   [应用到 PlayerPlay Mode]              │
└──────────────────────────────────────────────────────────────┘

9.8 构建验证器Build Validator

Tools > Zeling > Validate Build,在正式打包前执行完整检测:

检测项 级别
所有场景已加入 Build Settings Error
所有 AudioImportRulesSO 规则已应用 Warning
所有 AnimationEventConfigSO 无超出范围的事件时间 Error
所有 AbilityGate 引用的能力 ID 存在于 AbilityType 枚举 Error
所有 LocalizationKeys 常量在 zh-CN StringTable 中有对应条目 Warning
Player Prefab 已注册为 Addressableaddress = "Player",不使用 Resources/ Error
所有 Addressable Group 无缺失引用Missing Reference Error
SaveData JSON Schema 与当前字段一致(版本检查) Warning
构建目标平台 Build Target 与发布目标匹配 Warning

输出结果为 Build Validation ReportText + 每项跳转链接),只有零 Error 才允许继续打包。