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:
@@ -0,0 +1,103 @@
|
||||
"""离线资产验证模块。
|
||||
|
||||
检查解析数据中的资产引用是否有效:
|
||||
1. asset_key 是否在 asset_mapping 中定义
|
||||
2. 材质名是否在 material_mapping 中定义
|
||||
3. Content/ 目录下 .uasset 文件是否存在(可选)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
|
||||
from config.asset_mapping import MESH_ASSETS
|
||||
from config.material_mapping import MATERIAL_PATHS
|
||||
|
||||
logger = logging.getLogger("level_builder.validator")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""验证结果。"""
|
||||
total_actors: int = 0
|
||||
valid_actors: int = 0
|
||||
warnings: list[str] = None
|
||||
errors: list[str] = None
|
||||
missing_assets: set[str] = None
|
||||
missing_materials: set[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.warnings is None:
|
||||
self.warnings = []
|
||||
if self.errors is None:
|
||||
self.errors = []
|
||||
if self.missing_assets is None:
|
||||
self.missing_assets = set()
|
||||
if self.missing_materials is None:
|
||||
self.missing_materials = set()
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return len(self.errors) == 0
|
||||
|
||||
|
||||
def validate_actors(actors: list[dict], project_root: str = "") -> ValidationResult:
|
||||
"""验证 Actor 列表中的资产引用。
|
||||
|
||||
Args:
|
||||
actors: Actor 数据列表(来自解析器的 JSON 输出)。
|
||||
project_root: 项目根目录路径(用于文件存在性检查)。
|
||||
|
||||
Returns:
|
||||
ValidationResult 包含所有验证信息。
|
||||
"""
|
||||
result = ValidationResult(total_actors=len(actors))
|
||||
|
||||
for i, actor in enumerate(actors):
|
||||
name = actor.get("actor_name", f"<unnamed #{i}>")
|
||||
asset_key = actor.get("asset_key", "")
|
||||
material = actor.get("material", "")
|
||||
asset_type = actor.get("asset_type", "static_mesh")
|
||||
|
||||
# 跳过非 mesh 类型(光照、PlayerStart 等)
|
||||
if asset_type != "static_mesh":
|
||||
result.valid_actors += 1
|
||||
continue
|
||||
|
||||
# 检查 asset_key
|
||||
if not asset_key:
|
||||
result.warnings.append(f"{name}: 无 asset_key(可能是光照/特殊 Actor)")
|
||||
result.valid_actors += 1
|
||||
continue
|
||||
|
||||
if asset_key not in MESH_ASSETS:
|
||||
result.errors.append(f"{name}: 未知资产 '{asset_key}'")
|
||||
result.missing_assets.add(asset_key)
|
||||
continue
|
||||
|
||||
# 检查材质
|
||||
if material and material not in MATERIAL_PATHS:
|
||||
result.warnings.append(f"{name}: 未知材质 '{material}'")
|
||||
result.missing_materials.add(material)
|
||||
|
||||
# 检查 .uasset 文件存在性(如果提供了项目根目录)
|
||||
if project_root:
|
||||
mesh_info = MESH_ASSETS[asset_key]
|
||||
asset_path = mesh_info["path"]
|
||||
# /Game/BlockingStarterPack/Meshes/.../SM_Xxx.SM_Xxx → Content/BlockingStarterPack/Meshes/.../SM_Xxx.uasset
|
||||
fs_path = asset_path.replace("/Game/", "Content/").split(".")[0] + ".uasset"
|
||||
full_path = Path(project_root) / fs_path
|
||||
if not full_path.exists():
|
||||
result.warnings.append(f"{name}: 资产文件不存在 '{fs_path}'")
|
||||
|
||||
result.valid_actors += 1
|
||||
|
||||
# 汇总日志
|
||||
if result.missing_assets:
|
||||
logger.error(f"缺失资产定义: {sorted(result.missing_assets)}")
|
||||
if result.missing_materials:
|
||||
logger.warning(f"缺失材质定义: {sorted(result.missing_materials)}")
|
||||
logger.info(f"验证完成: {result.valid_actors}/{result.total_actors} 有效, "
|
||||
f"{len(result.errors)} 错误, {len(result.warnings)} 警告")
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user