feat(tools): 白模关卡自动生成Python脚本——Level Builder

两阶段流水线:搭建指南Markdown → JSON → UE5 Actor
- 解析器:4种表格变体+范围展开+坐标表达式计算
- 配置:37种BSP资产映射+13种材质+7区域定义
- 验证:265个Actor全部通过,0错误
- Builder:UE5 Python API自动放置Actor+材质+Outliner
- 报告:自动生成搭建报告Markdown

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 23:03:45 +08:00
parent 8011cb6472
commit 0f96b79dba
74 changed files with 10468 additions and 109 deletions
+211
View File
@@ -0,0 +1,211 @@
"""BSP 资产设计名 → UE5 内容路径映射。
数据来源: docs/technical/关卡-v01-搭建指南.md 第一章 (BSP 资产映射表)
资产根路径: Content/BlockingStarterPack/Meshes/
路径格式: /Game/<ContentSubPath>/<AssetName>.<AssetName>
"""
# 资产路径前缀
MESH_ROOT = "/Game/BlockingStarterPack/Meshes"
MATERIAL_ROOT = "/Game/BlockingStarterPack/Materials"
MESH_ASSETS = {
# ---- 建筑 — 墙体 ----
"Wall_400x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x300.SM_Wall_400x300",
"default_size": (400, 20, 300),
"category": "wall",
},
"Wall_200x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_200x300.SM_Wall_200x300",
"default_size": (200, 20, 300),
"category": "wall",
},
"Wall_100x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_100x300.SM_Wall_100x300",
"default_size": (100, 20, 300),
"category": "wall",
},
"Wall_400x100": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x100.SM_Wall_400x100",
"default_size": (400, 20, 100),
"category": "wall",
},
"WallDoor_200x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoor_200x300.SM_WallDoor_200x300",
"default_size": (200, 20, 300),
"category": "wall_door",
},
"WallDoorGarage_400x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoorGarage_400x300.SM_WallDoorGarage_400x300",
"default_size": (400, 20, 300),
"category": "wall_door",
},
"WallWindow_400x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindow_400x300.SM_WallWindow_400x300",
"default_size": (400, 20, 300),
"category": "wall_window",
},
"WallWindowSmall_200x300": {
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindowSmall_200x300.SM_WallWindowSmall_200x300",
"default_size": (200, 20, 300),
"category": "wall_window",
},
# ---- 建筑 — 地板 ----
"Floor_1000x1000": {
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_1000x1000.SM_Floor_1000x1000",
"default_size": (1000, 1000, 10),
"category": "floor",
},
"Floor_300x400": {
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_300x400.SM_Floor_300x400",
"default_size": (300, 400, 10),
"category": "floor",
},
"Floor_200x300": {
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_200x300.SM_Floor_200x300",
"default_size": (200, 300, 10),
"category": "floor",
},
# ---- 建筑 — 门 ----
"RotativeDoor_01": {
"path": f"{MESH_ROOT}/Architecture/Doors/SM_RotativeDoor_01.SM_RotativeDoor_01",
"default_size": None,
"category": "door",
},
"GarageDoorOpen_01": {
"path": f"{MESH_ROOT}/Architecture/Doors/SM_GarageDoorOpen_01.SM_GarageDoorOpen_01",
"default_size": None,
"category": "door",
},
# ---- 建筑 — 窗户 ----
"FakeWindowsFrame": {
"path": f"{MESH_ROOT}/Architecture/WindowsSet/SM_FakeWindowsFrame.SM_FakeWindowsFrame",
"default_size": None,
"category": "window",
},
# ---- 建筑 — 围栏/围挡 ----
"BarrierWall_600": {
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_600.SM_BarrierWall_600",
"default_size": (600, 20, 100),
"category": "barrier",
},
"BarrierWall_200": {
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_200.SM_BarrierWall_200",
"default_size": (200, 20, 100),
"category": "barrier",
},
"Fence_Small_400": {
"path": f"{MESH_ROOT}/Industrials/SM_Fence_Small_400.SM_Fence_Small_400",
"default_size": (400, 5, 100),
"category": "fence",
},
# ---- 建筑 — 横梁 ----
"Beam_300": {
"path": f"{MESH_ROOT}/Architecture/Beams/SM_Beam_300.SM_Beam_300",
"default_size": None,
"category": "beam",
},
# ---- 建筑 — 大型建筑 ----
"Building_01": {
"path": f"{MESH_ROOT}/BigBuildings/SM_Building_01.SM_Building_01",
"default_size": None,
"category": "building",
},
# ---- 家具 ----
"SimpleTable_01": {
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_01.SM_SimpleTable_01",
"default_size": None,
"category": "furniture",
},
"SimpleTable_02": {
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_02.SM_SimpleTable_02",
"default_size": None,
"category": "furniture",
},
"BedSingle": {
"path": f"{MESH_ROOT}/Bedroom/SM_BedSingle.SM_BedSingle",
"default_size": None,
"category": "furniture",
},
"Monitor_01": {
"path": f"{MESH_ROOT}/Furnitures/SM_Monitor_01.SM_Monitor_01",
"default_size": None,
"category": "furniture",
},
# ---- 工业/装饰 ----
"Barrel": {
"path": f"{MESH_ROOT}/Industrials/SM_Barrel.SM_Barrel",
"default_size": None,
"category": "industrial",
},
"WoodenBox_Small": {
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Small.SM_WoodenBox_Small",
"default_size": None,
"category": "industrial",
},
"WoodenBox_Normal": {
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Normal.SM_WoodenBox_Normal",
"default_size": None,
"category": "industrial",
},
"WoodenPallet_01": {
"path": f"{MESH_ROOT}/Industrials/SM_WoodenPallet_01.SM_WoodenPallet_01",
"default_size": None,
"category": "industrial",
},
"Bucket_01": {
"path": f"{MESH_ROOT}/Decorations/SM_Bucket_01.SM_Bucket_01",
"default_size": None,
"category": "industrial",
},
"TrashCans_01": {
"path": f"{MESH_ROOT}/Trashs/SM_TrashCans_01.SM_TrashCans_01",
"default_size": None,
"category": "industrial",
},
"Container_Normal": {
"path": f"{MESH_ROOT}/Industrials/SM_Container_Normal.SM_Container_Normal",
"default_size": None,
"category": "industrial",
},
# ---- 载具 ----
"Car01": {
"path": f"{MESH_ROOT}/Vehicles/SM_Car01.SM_Car01",
"default_size": None,
"category": "vehicle",
},
# ---- 基础体 ----
"Cube": {
"path": f"{MESH_ROOT}/Primitives/SM_Cube.SM_Cube",
"default_size": (100, 100, 100),
"category": "primitive",
},
"Cylinder": {
"path": f"{MESH_ROOT}/Primitives/SM_Cylinder.SM_Cylinder",
"default_size": (100, 100, 100),
"category": "primitive",
},
"CirclePlatform": {
"path": f"{MESH_ROOT}/Extra/SM_CirclePlatform.SM_CirclePlatform",
"default_size": None,
"category": "primitive",
},
# ---- 其他 ----
"Tree_01": {
"path": f"{MESH_ROOT}/Vegetations/SM_Tree_01.SM_Tree_01",
"default_size": None,
"category": "vegetation",
},
"RoadLamp_01": {
"path": f"{MESH_ROOT}/Lights/SM_RoadLamp_01.SM_RoadLamp_01",
"default_size": None,
"category": "light_fixture",
},
"DisplayPanels_400x200": {
"path": f"{MESH_ROOT}/DisplayPanels/SM_DisplayPanels_400x200.SM_DisplayPanels_400x200",
"default_size": None,
"category": "decoration",
},
}
@@ -0,0 +1,25 @@
"""标识色材质路径映射。
数据来源: docs/technical/关卡-v01-搭建指南.md §1.13
"""
MATERIAL_PATHS = {
# ---- Basics(标识色) ----
"MI_DevRed": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevRed.MI_DevRed",
"MI_DevOrange": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevOrange.MI_DevOrange",
"MI_DevGray": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGray.MI_DevGray",
"MI_DevBrown": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBrown.MI_DevBrown",
"MI_DevWhite": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevWhite.MI_DevWhite",
"MI_DevBlue": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlue.MI_DevBlue",
"MI_DevGreen": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGreen.MI_DevGreen",
"MI_DevSkyBlue": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevSkyBlue.MI_DevSkyBlue",
"MI_DevYellow": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevYellow.MI_DevYellow",
"MI_DevFoliage": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevFoliage.MI_DevFoliage",
"MI_DevPink": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPink.MI_DevPink",
# ---- Glass ----
"MI_DevGlassWhite": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassWhite.MI_DevGlassWhite",
"MI_DevGlassBlue": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassBlue.MI_DevGlassBlue",
}
# 材质在 Content/ 目录下的文件系统路径前缀(用于离线文件存在性验证)
MATERIAL_FS_PREFIX = "Content/BlockingStarterPack/Materials"
@@ -0,0 +1,36 @@
"""Primitives 替代规则。
数据来源: docs/technical/关卡-v01-搭建报告.md 第四节(缩放记录)
当搭建指南中直接指定了资产和缩放时,无需查此表。
此表用于未来执行案解析器,将语义化物件名映射到 Primitives 替代方案。
"""
SUBSTITUTION_RULES = {
# ---- 家具/家电 ----
"冰箱": {"asset": "Cube", "scale": (0.3, 0.3, 1.6), "material": "MI_DevWhite"},
"冰柜": {"asset": "Cube", "scale": (1.0, 0.6, 0.85), "material": "MI_DevWhite"},
"电视柜": {"asset": "SimpleTable_02", "scale": (0.85, 0.75, 0.67)},
# ---- 建筑细节 ----
"门垫": {"asset": "Cube", "scale": (0.6, 0.4, 0.02), "material": "MI_DevGray"},
"日历": {"asset": "Cube", "scale": (0.3, 0.02, 0.4), "material": "MI_DevRed"},
"招牌": {"asset": "Cube", "scale": (3.0, 0.05, 0.4), "material": "MI_DevBlue"},
"涂鸦": {"asset": "Cube", "scale": (1.2, 0.05, 0.8), "material": "MI_DevYellow"},
# ---- 玻璃 ----
"玻璃柜台": {"asset": "Cube", "scale": (2.0, 0.6, 0.9), "material": "MI_DevGlassWhite"},
"积水坑": {"asset": "Cube", "scale": (1.2, 0.8, 0.01), "material": "MI_DevGlassBlue"},
# ---- 街道/城郊物件 ----
"电话亭": {"asset": "Cube", "scale": (0.8, 0.8, 2.2), "material": "MI_DevGray"},
"电线杆": {"asset": "Cylinder", "scale": (0.04, 0.04, 12.0), "material": "MI_DevGray"},
"晾衣杆_柱": {"asset": "Cylinder", "scale": (0.05, 0.05, 3.6), "material": "MI_DevGray"},
"芦苇": {"asset": "Cylinder", "scale": (0.08, 0.08, 2.0), "material": "MI_DevFoliage"},
"花盆": {"asset": "Cylinder", "scale": (0.2, 0.2, 0.25), "material": "MI_DevGreen"},
# ---- 水塘 ----
"荷叶": {"asset": "CirclePlatform", "scale": (0.06, 0.06, 0.01), "material": "MI_DevGreen"},
"青蛙": {"asset": "Cube", "scale": (0.05, 0.05, 0.04), "material": "MI_DevGreen"},
"下水道盖": {"asset": "CirclePlatform", "scale": (0.12, 0.12, 0.02), "material": "MI_DevGray"},
"弹珠圈": {"asset": "CirclePlatform", "scale": (0.16, 0.16, 0.01), "material": "MI_DevGray"},
# ---- 自然 ----
"青苔": {"asset": "Cube", "scale": (0.6, 0.4, 0.01), "material": "MI_DevFoliage"},
"蜘蛛网": {"asset": "Cube", "scale": (0.3, 0.01, 0.3), "material": "MI_DevWhite"},
}
+142
View File
@@ -0,0 +1,142 @@
"""区域定义、Outliner 文件夹结构、Actor 命名规范。
数据来源:
- docs/design/prototype/关卡-v01-城郊小街-执行案.md §二
- docs/technical/关卡-v01-搭建指南.md §〇
"""
# ---- 场景全局参数 ----
SCENE_PARAMS = {
"scene_size": (8000, 6000), # cm
"ground_z": 0,
"character_height": 120, # cm
"camera_height": 100, # cm
"time_of_day": "15:00",
"origin_corner": "西南角地平面",
}
# ---- 区域定义 ----
ZONES = {
"Z1": {
"name": "家",
"name_en": "Home",
"origin": (0, 0, 0),
"bounds": (1000, 800),
"color_material": "MI_DevRed",
},
"Z2": {
"name": "小卖部",
"name_en": "CornerShop",
"origin": (1000, 200, 0),
"bounds": (600, 600),
"color_material": "MI_DevOrange",
},
"Z3": {
"name": "主街道",
"name_en": "MainStreet",
"origin": (0, 800, 0),
"bounds": (5000, 500),
"color_material": "MI_DevGray",
},
"Z4": {
"name": "空地",
"name_en": "VacantLot",
"origin": (3500, 1300, 0),
"bounds": (1500, 1200),
"color_material": "MI_DevBrown",
},
"Z5": {
"name": "巷子",
"name_en": "Alley",
"origin": (2300, 1300, 0),
"bounds": (300, 2000),
"color_material": "MI_DevWhite",
},
"Z6": {
"name": "水塘边",
"name_en": "Pond",
"origin": (1600, 3300, 0),
"bounds": (1500, 1400),
"color_material": "MI_DevSkyBlue",
},
"Z7": {
"name": "老居民楼",
"name_en": "OldBuilding",
"origin": (5800, 200, 0),
"bounds": (1200, 1800),
"color_material": "MI_DevGray",
},
}
# ---- Outliner 文件夹结构 ----
LEVEL_NAME = "LVL_SuburbanV01"
ZONE_FOLDERS = {
"Z1": f"{LEVEL_NAME}/Z1_家",
"Z2": f"{LEVEL_NAME}/Z2_小卖部",
"Z3": f"{LEVEL_NAME}/Z3_主街道",
"Z4": f"{LEVEL_NAME}/Z4_空地",
"Z5": f"{LEVEL_NAME}/Z5_巷子",
"Z6": f"{LEVEL_NAME}/Z6_水塘边",
"Z7": f"{LEVEL_NAME}/Z7_老居民楼",
"GLOBAL": f"{LEVEL_NAME}/Global",
}
CATEGORY_FOLDERS = {
"STR": "Structure",
"PROP": "Props",
"INTR": "Interactables",
"COLL": "Collectibles",
"BND": "Boundaries",
"LGT": "Lighting",
}
GLOBAL_SUBFOLDERS = {
"BND": f"{LEVEL_NAME}/Global/Boundaries",
"LGT": f"{LEVEL_NAME}/Global/Lighting",
"PLAY": f"{LEVEL_NAME}/Global/PlayerStart",
}
def get_folder_path(zone_id: str, category: str) -> str:
"""根据区域 ID 和类别获取 Outliner 文件夹路径。"""
if zone_id == "GLOBAL":
return GLOBAL_SUBFOLDERS.get(category, f"{LEVEL_NAME}/Global")
base = ZONE_FOLDERS.get(zone_id, f"{LEVEL_NAME}/Global")
sub = CATEGORY_FOLDERS.get(category, "")
if sub:
return f"{base}/{sub}"
return base
# ---- Actor 命名模板 ----
# LVL_<ZoneID>_<Category>_<Description>
# 已在搭建指南中给出完整名称,脚本直接使用。
# ---- 搭建顺序 ----
BUILD_ORDER = ["Z1", "Z3", "Z2", "Z4", "Z5", "Z6", "Z7"]
# ---- Player Start ----
PLAYER_START = {
"location": (500, 720, 0),
"rotation": (0, 0, 0), # 面向 +Y(北)
}
# ---- 光照 ----
LIGHTING = {
"directional_light": {
"location": (0, 0, 500),
"rotation": (-45, 45, 0), # 从西南照射,仰角45°
"intensity": 3.0,
"color": (1.0, 0.95, 0.85), # 暖白
},
"sky_light": {
"intensity": 0.5,
},
"street_lights": [
{"location": (1500, 1050, 0), "intensity": 0.3},
{"location": (3000, 1050, 0), "intensity": 0.3},
{"location": (4500, 1050, 0), "intensity": 0.3},
],
}