"""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()