Files
starmen1265 0f96b79dba 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>
2026-06-07 23:03:45 +08:00

104 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""离线资产验证模块。
检查解析数据中的资产引用是否有效:
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