From 59d352d3853ae640dbef9e2d83c66c0df35ae54c Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Tue, 9 Jun 2026 00:08:17 +0800 Subject: [PATCH] =?UTF-8?q?docs(tech):=20Level=20Builder=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=A2=B3=E7=90=86=E6=8A=A5=E5=91=8A=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=9E=B6=E6=9E=84/=E6=B5=81=E7=A8=8B/=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../笔记/level-builder-逻辑梳理报告.md | 448 ++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 docs/technical/笔记/level-builder-逻辑梳理报告.md diff --git a/docs/technical/笔记/level-builder-逻辑梳理报告.md b/docs/technical/笔记/level-builder-逻辑梳理报告.md new file mode 100644 index 0000000..62f3d55 --- /dev/null +++ b/docs/technical/笔记/level-builder-逻辑梳理报告.md @@ -0,0 +1,448 @@ +# Level Builder 技能逻辑梳理报告 + +> 生成时间: 2026-06-09 +> 梳理范围: `tools/level-builder/` 全部源码 + `.claude/skills/level-builder/SKILL.md` + +--- + +## 一、整体架构 + +``` +搭建指南.md ──parse──▶ JSON 中间数据 ──validate──▶ 验证报告 + │ + build (UE5内) + │ + ▼ + .umap 关卡文件 + build_log.json + │ + report + │ + ▼ + 搭建报告.md +``` + +四个阶段,顺序执行,前三步均可独立离线运行(无需 UE5),仅 `build` 需要 `unreal` 模块。 + +--- + +## 二、文件清单与职责 + +``` +tools/level-builder/ +├── main.py # CLI 入口,子命令分派 (parse/validate/build/report) +├── build_level.bat # Windows 批处理,自动检测 UE5 并以命令行模式启动构建 +├── build_cmdline.py # 命令行构建 Python 入口(由 bat 通过 -ExecutePythonScript 调用) +├── build_in_editor.py # 编辑器内手动执行的入口脚本(Output Log Python 控制台) +├── check_pivots.py / .bat # 锚点检查工具 +├── parsers/ +│ ├── markdown_table.py # 通用 Markdown 管道表格解析 + 坐标/旋转/缩放解析工具函数 +│ └── build_guide_parser.py # 核心解析器——按 Phase 切割搭建指南,生成 ActorData 列表 +├── config/ +│ ├── asset_mapping.py # 38 种 BSP 资产 → UE5 路径 + 锚点偏移 + get_pivot_compensation() +│ ├── material_mapping.py # 13 种标识色材质 → UE5 路径 +│ ├── zone_config.py # 7 区域定义 + Outliner 文件夹结构 + get_folder_path() +│ └── substitution_rules.py # 语义化物件名 → Primitives 替代方案(预留给未来,当前未被导入) +├── builder/ +│ └── scene_builder.py # 核心构建器——UE5 Python 环境中 spawn Actor、设 Transform/材质/Outliner +├── validation/ +│ └── asset_validator.py # 离线验证:资产 key / 材质 / .uasset 文件存在性 +├── report/ +│ └── report_generator.py # JSON + build_log → Markdown 搭建报告 +└── output/ # 生成物目录 (JSON、build_log、报告) +``` + +### 模块调用关系 + +| 模块 | 被谁调用 | 依赖模块 | +|------|---------|---------| +| `main.py` | 用户 / Claude 命令行 | `build_guide_parser`, `asset_validator`, `scene_builder`, `report_generator` | +| `build_level.bat` | 用户双击或命令行 | `build_cmdline.py`(通过 UE5 `-ExecutePythonScript`) | +| `build_cmdline.py` | `build_level.bat` | `scene_builder` (`build_level` + `setup_post_build`) | +| `build_in_editor.py` | 用户在 UE5 Python 控制台执行 | `scene_builder` (`build_level`) | +| `build_guide_parser.py` | `main.py cmd_parse()` | `markdown_table`, `zone_config` | +| `scene_builder.py` | `build_cmdline.py`, `build_in_editor.py`, `main.py cmd_build()` | `asset_mapping`, `material_mapping` | +| `asset_validator.py` | `main.py cmd_validate()` | `asset_mapping`, `material_mapping` | +| `report_generator.py` | `main.py cmd_report()` | `asset_mapping`(仅缩放记录部分) | +| `substitution_rules.py` | **无(死代码)** | — | + +--- + +## 三、核心流程详解 + +### 3.1 解析流程(`BuildGuideParser`) + +解析器按 Phase 切割搭建指南文本,核心是**状态机**模式——维护 `current_zone` / `current_category` 状态,遇到标题行切换状态,遇到表格行按当前状态生成 Actor。 + +#### Phase 划分与解析顺序 + +``` +_parse_global_info() → metadata(当前为硬编码,未从文档提取) + ↓ +_parse_phase_ground() → 地面铺设表格(#### Z1 家 地面 / #### 全局地面) + ↓ +_parse_phase_boundaries() → 边界围挡表格(支持 ~ 范围展开,如 01~15) + ↓ +_parse_phase_zones() → 逐区域搭建(### C1. Z1 — 家 → #### C1.1 结构体) + ↓ +_parse_phase_global_objects() → 全局物件(路灯、垃圾桶等) + ↓ +_parse_phase_lighting() → 光照配置(DirectionalLight / SkyLight / PointLight) + ↓ +_add_player_start() → 硬编码 PlayerStart 位置 + ↓ +_deduplicate_actors() → 同名 + 同位置 + 同资产 → 去重 + ↓ +_build_summary() → 构建统计摘要 +``` + +#### 关键设计点 + +**1. 范围展开(`_expand_range_row`)** + +输入格式:`LVL_BND_East_01~15` + `(8000, 200+400n, 0) n=0~14` + +解析流程: +- 正则 `(.+?)_(\d+)~(\d+)` 提取前缀、起始编号、结束编号 +- 正则 `n\s*=\s*(\d+)~(\d+)` 提取 n 的范围 +- 去掉 `n=xx~yy` 后缀后解析三个坐标表达式 +- 对每个 n 值调用 `_eval_coord_expr()` 计算坐标 +- 生成 N 个 ActorData,编号自动补零(`{prefix}_{num:02d}`) + +**2. 坐标表达式计算(`_eval_coord_expr`)** + +支持格式: +- 纯数字:`8000` +- 含 n 的简单算术:`200+400n` → 替换为 `200+400*{n}` → `eval()` + +**3. 表格类型推断(`_parse_zone_row`)** + +通过列名判断: +- 有 `"触发半径"` 列 → 交互物表(`_parse_interactable_row`) +- 有 `"稀有度"` 列 → 收集品表(`_parse_collectible_row`) +- 否则 → 结构体/布景表(`_parse_structure_or_prop_row`) + +**4. 占位规则** + +| 类型 | 占位资产 | 缩放 | 默认材质 | 说明 | +|------|---------|------|---------|------| +| 交互物 | Cube | (0.3, 0.3, 0.3) | MI_DevYellow | 交互物占位标记 | +| 收集品 | CirclePlatform | (0.03, 0.03, 0.03) | MI_DevWhite / MI_DevYellow | 普通=白,稀有用金色 | + +**5. 去重逻辑** + +- 同名 + 同位置 + 同资产 → 视为完全重复,移除 +- 同名但位置或资产不同 → 合法保留(如 Z3 墙段) + +### 3.2 构建流程(`scene_builder.build_level`) + +#### 主循环 + +```python +for actor_def in actors: + match asset_type: + "player_start" → _spawn_player_start() + "directional_light" → _spawn_directional_light() + "sky_light" → _spawn_sky_light() + "point_light" → _spawn_point_light() + "exp_height_fog" → _spawn_exp_height_fog() + _ (default) → _spawn_mesh_actor() +``` + +#### `_spawn_mesh_actor` 详细流程 + +``` +1. 查 MESH_ASSETS 获取 UE5 资产路径 + └─ 找不到 → 回退到 Cube + "[MISSING]" 前缀 + MI_DevPink + +2. unreal.load_asset(mesh_path) + └─ 加载失败 → 同上 Cube 回退 + +3. 锚点补偿计算 + offset = pivot_offset_xy × scale + rotated_offset = rotate(offset, yaw) + spawn_pos = guide_pos - rotated_offset + +4. editor_subsystem.spawn_actor_from_object(static_mesh, spawn_pos) + +5. 设置 Rotation(关键字参数:pitch=/yaw=/roll=,避免 UE5.7 位置参数歧义) + +6. 设置 Scale + +7. 设置材质(查 MATERIAL_PATHS → load_asset → set_material(0, material)) + +8. 设置 Actor Label 和 Outliner Folder +``` + +#### 后处理(`setup_post_build` + `build_cmdline.py` 额外逻辑) + +| 步骤 | 位置 | 功能 | +|------|------|------| +| 添加 ExponentialHeightFog | `build_cmdline.py` | 雾密度 0.015,自动检测是否已存在 | +| 设置 GameMode | `scene_builder.setup_post_build()` | BP_ThirdPersonGameMode(通过 SoftClassPath) | +| 启用大气效果 | `scene_builder.setup_post_build()` | DirectionalLight 的 `atmosphere_sun_light = True` | +| 保存关卡 | `build_cmdline.py` | `EditorLevelLibrary.save_current_level()` | +| 退出编辑器 | `build_cmdline.py` | `SystemLibrary.execute_console_command(None, "quit")` | + +### 3.3 锚点补偿系统 + +`asset_mapping.py` 中每个资产记录了实测数据: + +```python +"Wall_400x300": { + "path": "...", + "default_size": (400, 20, 300), + "actual_bounds": ((0, -10, 0), (400, 10, 300)), # min, max(实测) + "pivot_offset_xy": (200, 0), # 锚点偏离视觉中心的 XY 偏移 + "category": "wall", +} +``` + +#### 锚点类型分类 + +| 类型 | 典型资产 | 锚点位置 | offset_xy | +|------|---------|---------|-----------| +| 角落锚点(墙体) | Wall_400x300, Wall_200x300 | 底边一端 | (半宽, 0) | +| 角落锚点(地板) | Floor_1000x1000, Floor_300x400 | 角落 | (半宽, 半深) | +| 角落锚点(Cube) | Cube | 角落 (0,0,0) | (50, 50) | +| 底面中心 | Cylinder, 家具类, 工业类 | 底面中心 | (0, 0) | +| 偏心锚点 | BedSingle, RoadLamp_01 | 接近角落/边缘 | 需逐个实测 | + +#### 补偿公式 + +``` +offset_scaled = pivot_offset_xy × [sx, sy] +offset_rotated = rotate_z(offset_scaled, yaw) // 绕 Z 轴旋转 +spawn_pos = guide_pos - offset_rotated +``` + +### 3.4 验证流程(`asset_validator`) + +三级检查: + +1. **资产 key 检查**:`asset_key` 是否在 `MESH_ASSETS` 字典中注册 +2. **材质检查**:`material` 是否在 `MATERIAL_PATHS` 字典中注册(warning 级别) +3. **文件存在性检查**:如果提供了 `project_root`,检查 `.uasset` 文件是否实际存在(warning 级别) + +跳过规则:`asset_type != "static_mesh"` 的 Actor(光照、PlayerStart 等)直接标记为有效。 + +### 3.5 报告生成(`report_generator`) + +输出结构: + +``` +# 白模搭建报告 — 自动生成 + +## 一、搭建概要 + - 总 Actor 数、按类别统计、构建成功/失败数 + +## 二、逐区域对照 + - 按 Z1~Z7 + GLOBAL 顺序 + - 每区域一张表格:# / Actor名称 / 资产 / 位置 / 缩放 / 材质 / 状态(✅/❌) + +## 三、资产使用统计 + - 按使用次数降序排列的资产使用表 + +## 四、缩放记录 + - 仅记录缩放 != (1,1,1) 的 Actor + - 包含实际尺寸计算(default_size × scale) +``` + +--- + +## 四、构建入口对比 + +| 特性 | `build_level.bat` → `build_cmdline.py` | `build_in_editor.py` | +|------|---------------------------------------|---------------------| +| 运行方式 | 命令行一键启动 UE5 | 用户手动打开 UE5 编辑器 | +| 项目路径 | 通过环境变量 `LB_PROJECT_ROOT` 传入 | **硬编码** `E:/MiceCola/work/ChildHood` | +| JSON 路径 | 通过环境变量 `LB_JSON_PATH` 传入 | 硬编码 `output/level_v01.json` | +| 关卡创建 | `ensure_map()` 自动创建/加载 | 需用户手动打开目标关卡 | +| 后处理 | ✅ 含 ExponentialHeightFog + GameMode + 大气 | ❌ 无后处理 | +| 保存 | ✅ 自动保存 | ❌ 需用户手动保存 | +| 区域过滤 | 支持(通过 `--zones`) | 代码中写死 `zones=None` | +| 推荐度 | ⭐ 推荐 | 仅调试用 | + +--- + +## 五、数据流总结 + +``` +搭建指南.md + │ + ├─ 第〇章 全局参数 ──→ metadata(当前硬编码,未动态解析) + ├─ 第一章 资产映射表 ──→(未解析,仅验证用) + ├─ 第二章 地面铺设 ──→ _parse_phase_ground() ──→ STR Actors(地板) + ├─ 第二章 边界围挡 ──→ _parse_phase_boundaries() ──→ BND Actors(含范围展开) + ├─ 第三章 逐区域 ──→ _parse_phase_zones() ──→ STR / PROP / INTR / COLL Actors + ├─ 第三章 全局物件 ──→ _parse_phase_global_objects() ──→ PROP / LGT Actors + ├─ 第四章 光照 ──→ _parse_phase_lighting() ──→ LGT Actors(DL / SkyLight / PointLight) + └─ 硬编码 ──→ _add_player_start() ──→ PLAY Actor + │ + ▼ + JSON: { metadata, actors: [...], summary } + │ + ├─ validate ──→ 检查 asset_key / material / .uasset 存在性 + │ + ├─ build ──→ UE5 spawn + 锚点补偿 + 后处理 ──→ .umap + build_log.json + │ + └─ report ──→ Markdown 搭建报告(逐区域对照表 + 资产统计 + 缩放记录) +``` + +--- + +## 六、发现的问题和改进建议 + +### ⚠️ 问题 1:`_eval_coord_expr` 使用 `eval()` 存在注入风险 + +**位置**: `parsers/build_guide_parser.py:411` + +```python +result = eval(expr) # 受限输入,仅数字和基本运算 +``` + +**风险**: `expr` 来自 Markdown 文本内容,理论上任何字符串都可能被注入执行。 + +**建议**: 改用简单的算术表达式解析器,只支持 `+`、`-`、`*`、`/`、数字和变量 `n`。例如用 `ast.literal_eval` 的思路做白名单解析,或用正则拆分后逐项计算。 + +**严重程度**: 低(当前为单人开发、固定格式文档,实际风险极小,但作为工程规范应修复) + +--- + +### ⚠️ 问题 2:`_parse_global_info` 硬编码 metadata + +**位置**: `parsers/build_guide_parser.py:156-163` + +```python +self.metadata = { + "source": "关卡-v01-搭建指南.md", # 硬编码! + "scene_size": [8000, 6000], + "ground_z": 0, + "character_height": 120, + "camera_height": 100, +} +``` + +**影响**: 切换到 v02 搭建指南时,metadata 中的 source 仍然是 v01,场景尺寸等参数也不会自动更新。 + +**建议**: 从搭建指南第〇章的表格中动态解析 metadata,与解析其他 Phase 表格使用相同的基础设施(`markdown_table.parse_table`)。 + +**严重程度**: 中(多版本场景下会导致报告信息不准确) + +--- + +### ⚠️ 问题 3:`build_in_editor.py` 硬编码绝对路径 + +**位置**: `build_in_editor.py:18` + +```python +PROJECT_ROOT = Path(r"E:/MiceCola/work/ChildHood") +``` + +**影响**: 不同机器或目录结构下会失效。 + +**建议**: 参照 `build_cmdline.py` 的做法,优先使用环境变量,fallback 到 `unreal.Paths.project_dir()` 自动检测。 + +**严重程度**: 低(推荐入口是 `build_level.bat`,`build_in_editor.py` 仅用于调试) + +--- + +### ⚠️ 问题 4:`substitution_rules.py` 是死代码 + +**现状**: 该模块未被任何其他模块导入使用。设计意图是为未来"执行案解析器"预留——当直接从执行案而非搭建指南生成关卡时,将语义化物件名(如"冰箱")映射到 Primitives 替代方案。 + +**建议**: 当前无需处理,但建议在模块顶部 docstring 中明确标注"预留接口,当前未被使用"。 + +**严重程度**: 无(仅代码整洁性问题) + +--- + +### 💡 问题 5:SKILL.md 区域一览表与 `zone_config.py` 不一致 + +**SKILL.md 中的表格**: + +| Zone | origin | bounds | +|------|--------|--------| +| Z5 巷子 | (2000, 0, 0) | 600×800 | +| Z6 水塘边 | (5500, 0, 0) | 1500×1000 | +| Z7 老居民楼 | (5000, 1300, 0) | 1500×1200 | + +**`zone_config.py` 中的实际值**: + +| Zone | origin | bounds | +|------|--------|--------| +| Z5 巷子 | (2300, 1300, 0) | 300×2000 | +| Z6 水塘边 | (1600, 3300, 0) | 1500×1400 | +| Z7 老居民楼 | (5800, 200, 0) | 1200×1800 | + +**判断**: `zone_config.py` 是经过 v01 bugfix 修正后的实际在用值,SKILL.md 表格是旧版本。以代码为准。 + +**建议**: 更新 SKILL.md 中的区域一览表,使其与 `zone_config.py` 一致。 + +**严重程度**: 低(不影响功能,仅文档准确性) + +--- + +### 💡 问题 6:`_find_section_start` 简单字符串匹配可能误命中 + +**位置**: `parsers/build_guide_parser.py:787-791` + +```python +def _find_section_start(self, lines, keyword): + for i, line in enumerate(lines): + if keyword in line: + return i + return -1 +``` + +**风险**: 如果关键词出现在非标题行(如表格内容、注释中),会误匹配。 + +**现状**: 搭建指南格式固定,当前不会出问题。 + +**建议**: 如需提高鲁棒性,可增加标题行检测(如检查行首是否有 `##` 前缀)。 + +**严重程度**: 低 + +--- + +### 💡 问题 7:LEVEL_NAME 硬编码为 `LVL_SuburbanV01` + +**位置**: `config/zone_config.py:72` + +```python +LEVEL_NAME = "LVL_SuburbanV01" +``` + +**影响**: Outliner 文件夹路径和 Global 子文件夹路径都依赖这个常量。v02 版本时需要手动修改。 + +**建议**: 将 LEVEL_NAME 提升为 CLI 参数或从 JSON metadata 中读取。 + +**严重程度**: 低(当前手动改一行即可) + +--- + +## 七、总结 + +### 架构评价 + +- **模块划分清晰**:解析 / 配置 / 构建 / 验证 / 报告各司其职,耦合度低 +- **数据驱动设计好**:资产映射、材质映射、区域配置全部 externalize,新增资产/区域只需改 config +- **锚点补偿系统完善**:38 种资产逐一实测,含旋转补偿,解决了 BSP 资产锚点不一致的核心问题 +- **双入口设计合理**:命令行自动构建(推荐)+ 编辑器内手动执行(调试) + +### 代码质量 + +- 整体代码质量良好,注释充分 +- 错误处理较完善(资产加载失败有回退,编辑器未就绪有超时等待) +- 命名规范统一(Actor 命名、文件夹结构、日志前缀) + +### 风险点 + +| 风险 | 严重度 | 修复优先级 | +|------|--------|-----------| +| `eval()` 注入 | 低 | P3(单人开发场景风险极小) | +| metadata 硬编码 | 中 | P2(多版本场景下需修复) | +| `build_in_editor.py` 硬编码路径 | 低 | P3(推荐用 bat 入口) | +| SKILL.md 区域表过时 | 低 | P3(文档同步) | +| LEVEL_NAME 硬编码 | 低 | P3(手动改一行即可) | +| `substitution_rules.py` 死代码 | 无 | P4(代码整洁) | +| `_find_section_start` 误匹配风险 | 低 | P4(当前格式固定无问题) |