0f96b79dba
两阶段流水线:搭建指南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>
200 lines
6.9 KiB
Python
200 lines
6.9 KiB
Python
"""Level Builder CLI — 白模关卡自动生成工具。
|
|
|
|
用法:
|
|
# Stage 1: 解析搭建指南 → JSON
|
|
python main.py parse --input ../../docs/technical/关卡-v01-搭建指南.md --output output/level_v01.json
|
|
|
|
# Stage 1.5: 验证
|
|
python main.py validate --input output/level_v01.json [--project-root ../..]
|
|
|
|
# Stage 2: UE5 编辑器内构建(需在编辑器 Python 环境中运行)
|
|
# 方式 A: 在 Output Log Python 控制台:
|
|
# exec(open(r"tools/level-builder/main.py").read())
|
|
# main(["build", "--input", "output/level_v01.json"])
|
|
# 方式 B: File > Execute Python Script
|
|
|
|
# Stage 3: 生成报告
|
|
python main.py report --input output/level_v01.json [--build-log output/build_log.json] --output report.md
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# 将项目 tools 目录加入 sys.path
|
|
_TOOLS_DIR = Path(__file__).resolve().parent
|
|
_LEVEL_BUILDER_DIR = _TOOLS_DIR
|
|
_PROJECT_ROOT = _TOOLS_DIR.parent.parent
|
|
|
|
if str(_TOOLS_DIR) not in sys.path:
|
|
sys.path.insert(0, str(_TOOLS_DIR))
|
|
if str(_PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
|
|
# 配置日志
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="[%(levelname)s] %(name)s: %(message)s",
|
|
)
|
|
logger = logging.getLogger("level_builder")
|
|
|
|
|
|
def cmd_parse(args):
|
|
"""解析搭建指南 Markdown → JSON。"""
|
|
from parsers.build_guide_parser import BuildGuideParser
|
|
|
|
input_path = args.input
|
|
output_path = args.output or "output/level_data.json"
|
|
|
|
logger.info(f"解析搭建指南: {input_path}")
|
|
|
|
parser = BuildGuideParser()
|
|
data = parser.parse_file(input_path)
|
|
|
|
# 写入 JSON
|
|
out_path = Path(output_path)
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(out_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
summary = data.get("summary", {})
|
|
logger.info(f"解析完成: {summary.get('total_actors', 0)} 个 Actor")
|
|
logger.info(f" 按类别: {summary.get('by_category', {})}")
|
|
logger.info(f" 按区域: {summary.get('by_zone', {})}")
|
|
logger.info(f" 唯一资产数: {summary.get('unique_assets', 0)}")
|
|
logger.info(f"输出: {out_path.resolve()}")
|
|
|
|
|
|
def cmd_validate(args):
|
|
"""验证 JSON 数据中的资产引用。"""
|
|
from validation.asset_validator import validate_actors
|
|
|
|
input_path = args.input
|
|
project_root = args.project_root or str(_PROJECT_ROOT)
|
|
|
|
logger.info(f"验证: {input_path}")
|
|
|
|
with open(input_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
actors = data.get("actors", [])
|
|
result = validate_actors(actors, project_root)
|
|
|
|
if result.is_valid:
|
|
logger.info("✅ 验证通过!")
|
|
else:
|
|
logger.error(f"❌ 验证失败: {len(result.errors)} 个错误")
|
|
for err in result.errors:
|
|
logger.error(f" {err}")
|
|
|
|
if result.warnings:
|
|
for warn in result.warnings:
|
|
logger.warning(f" {warn}")
|
|
|
|
logger.info(f"有效 Actor: {result.valid_actors}/{result.total_actors}")
|
|
|
|
# 保存验证结果
|
|
output_path = Path(input_path).parent / "validation_result.json"
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
json.dump({
|
|
"is_valid": result.is_valid,
|
|
"total": result.total_actors,
|
|
"valid": result.valid_actors,
|
|
"errors": result.errors,
|
|
"warnings": result.warnings,
|
|
"missing_assets": sorted(result.missing_assets),
|
|
"missing_materials": sorted(result.missing_materials),
|
|
}, f, ensure_ascii=False, indent=2)
|
|
logger.info(f"验证结果: {output_path}")
|
|
|
|
|
|
def cmd_build(args):
|
|
"""在 UE5 编辑器中构建关卡。"""
|
|
try:
|
|
from builder.scene_builder import build_level
|
|
except ImportError:
|
|
print("[错误] 无法导入 builder 模块。确保在 UE5 编辑器 Python 环境中运行。")
|
|
print(" 在 Output Log 中切换到 Python,然后执行:")
|
|
print(f' exec(open(r"{Path(__file__).resolve()}").read())')
|
|
print(f' build_level(r"{Path(args.input).resolve()}")')
|
|
return
|
|
|
|
zones = args.zones.split(",") if args.zones else None
|
|
build_level(args.input, clear_existing=args.clear, zones=zones)
|
|
|
|
|
|
def cmd_report(args):
|
|
"""生成搭建报告。"""
|
|
from report.report_generator import generate_report
|
|
|
|
input_path = args.input
|
|
output_path = args.output or "output/搭建报告.md"
|
|
build_log_path = args.build_log
|
|
|
|
logger.info(f"生成报告: {input_path}")
|
|
|
|
with open(input_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
build_log = None
|
|
if build_log_path:
|
|
try:
|
|
with open(build_log_path, "r", encoding="utf-8") as f:
|
|
build_log = json.load(f)
|
|
except FileNotFoundError:
|
|
logger.warning(f"构建日志不存在: {build_log_path},生成无状态报告")
|
|
|
|
report = generate_report(data, build_log, output_path)
|
|
logger.info(f"报告已生成: {output_path}")
|
|
|
|
|
|
def main(argv=None):
|
|
"""CLI 入口。"""
|
|
parser = argparse.ArgumentParser(
|
|
description="ChildHood 白模关卡自动生成工具",
|
|
prog="level-builder",
|
|
)
|
|
subparsers = parser.add_subparsers(dest="command", help="子命令")
|
|
|
|
# parse
|
|
p_parse = subparsers.add_parser("parse", help="解析搭建指南 Markdown → JSON")
|
|
p_parse.add_argument("--input", "-i", required=True, help="搭建指南 Markdown 文件路径")
|
|
p_parse.add_argument("--output", "-o", default="output/level_data.json", help="输出 JSON 路径")
|
|
|
|
# validate
|
|
p_validate = subparsers.add_parser("validate", help="验证 JSON 数据")
|
|
p_validate.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
|
p_validate.add_argument("--project-root", "-r", help="项目根目录(用于文件存在性检查)")
|
|
|
|
# build
|
|
p_build = subparsers.add_parser("build", help="在 UE5 中构建关卡")
|
|
p_build.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
|
p_build.add_argument("--clear", action="store_true", default=True, help="清除已有 LVL_* Actor")
|
|
p_build.add_argument("--no-clear", dest="clear", action="store_false", help="不清除已有 Actor")
|
|
p_build.add_argument("--zones", "-z", help="仅构建指定区域,逗号分隔(如 Z1,Z3)")
|
|
|
|
# report
|
|
p_report = subparsers.add_parser("report", help="生成搭建报告")
|
|
p_report.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
|
p_report.add_argument("--build-log", "-l", help="构建日志 JSON 路径")
|
|
p_report.add_argument("--output", "-o", default="output/搭建报告.md", help="报告输出路径")
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.command == "parse":
|
|
cmd_parse(args)
|
|
elif args.command == "validate":
|
|
cmd_validate(args)
|
|
elif args.command == "build":
|
|
cmd_build(args)
|
|
elif args.command == "report":
|
|
cmd_report(args)
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|