"""UE5 命令行白模关卡构建脚本。 由 build_level.bat 通过 UE5 的 -ExecutePythonScript 参数调用。 在 UE5 编辑器 Python 环境中运行,无需手动打开编辑器。 用法: build_level.bat # 默认配置 build_level.bat --json output/v02.json # 指定 JSON build_level.bat --no-quit # 构建后不退出编辑器 环境变量(由 build_level.bat 设置): LB_JSON_PATH — JSON 数据文件路径(相对于项目根目录) LB_MAP_NAME — 目标关卡资产名 LB_MAP_PACKAGE — 关卡包路径(如 /Game/Maps) LB_NO_QUIT — 设为 "1" 不退出编辑器 """ import json import os import sys import time from pathlib import Path # ---- 从环境变量读取配置 ---- json_rel = os.environ.get("LB_JSON_PATH", "tools/level-builder/output/level_v01.json") map_name = os.environ.get("LB_MAP_NAME", "LVL_SuburbanV01") map_package = os.environ.get("LB_MAP_PACKAGE", "/Game/Maps") no_quit = os.environ.get("LB_NO_QUIT", "0") == "1" map_asset_path = f"{map_package}/{map_name}" # ---- 检查 unreal 模块 ---- try: import unreal except ImportError: print("[LevelBuilder] FATAL: unreal 模块不可用,必须在 UE5 环境中运行") sys.exit(1) # ---- 项目根目录 ---- # UE5 -ExecutePythonScript 模式下 CWD 是引擎目录,不是项目目录 # 所以必须通过环境变量传入项目路径 _project_root_str = os.environ.get("LB_PROJECT_ROOT", "") if _project_root_str: PROJECT_ROOT = Path(_project_root_str) else: # Fallback: 尝试从 .uproject 文件定位 _cwd = Path(os.getcwd()) if (_cwd / "ChildHood.uproject").exists(): PROJECT_ROOT = _cwd else: # 最后尝试 unreal API try: PROJECT_ROOT = Path(unreal.Paths.project_dir()).parent except Exception: PROJECT_ROOT = _cwd json_path = PROJECT_ROOT / json_rel print(f"[LevelBuilder] ==========================================") print(f"[LevelBuilder] ChildHood Level Builder — Command Line") print(f"[LevelBuilder] ==========================================") print(f"[LevelBuilder] Project: {PROJECT_ROOT}") print(f"[LevelBuilder] JSON: {json_rel}") print(f"[LevelBuilder] Map: {map_asset_path}") print(f"[LevelBuilder]") # ---- 等待编辑器子系统就绪 ---- def wait_for_editor(timeout=90): """轮询等待 EditorActorSubsystem 可用。""" print("[LevelBuilder] 等待编辑器初始化...") start = time.time() while time.time() - start < timeout: try: subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) if subsys is not None: elapsed = time.time() - start print(f"[LevelBuilder] 编辑器就绪 ({elapsed:.1f}s)") return True except Exception: pass time.sleep(1) print(f"[LevelBuilder] FATAL: 编辑器初始化超时 ({timeout}s)") return False if not wait_for_editor(): sys.exit(1) # ---- 设置 sys.path ---- lb_dir = str(PROJECT_ROOT / "tools" / "level-builder") for p in [str(PROJECT_ROOT), lb_dir]: if p not in sys.path: sys.path.insert(0, p) # ---- 确保目标关卡存在 ---- def ensure_map(asset_path): """创建或加载目标关卡。成功返回 True。""" # 已存在 → 加载 if unreal.EditorAssetLibrary.does_asset_exist(asset_path): print(f"[LevelBuilder] 加载已有关卡: {asset_path}") unreal.EditorLevelLibrary.load_level(asset_path) return True print(f"[LevelBuilder] 创建新关卡: {asset_path}") # 方法 1: new_level(asset_path) — UE5.7 EditorLevelLibrary 直接创建 try: unreal.EditorLevelLibrary.new_level(asset_path) print("[LevelBuilder] ✅ 关卡创建成功 (new_level)") return True except Exception as e: print(f"[LevelBuilder] new_level 失败: {e}") # 方法 2: AssetTools + WorldFactory(某些版本可用) try: asset_tools = unreal.get_editor_subsystem(unreal.AssetToolsSubsystem) factory = unreal.WorldFactory() new_asset = asset_tools.create_asset( asset_name=map_name, package_path=map_package, asset_class=None, factory=factory, ) if new_asset: unreal.EditorLevelLibrary.load_level(asset_path) print("[LevelBuilder] ✅ 关卡创建成功 (WorldFactory)") return True except Exception as e: print(f"[LevelBuilder] WorldFactory 失败: {e}") print("[LevelBuilder] FATAL: 无法创建关卡") return False if not ensure_map(map_asset_path): sys.exit(1) # ---- 验证 JSON ---- if not json_path.exists(): print(f"[LevelBuilder] FATAL: JSON 文件不存在: {json_path}") sys.exit(1) # ---- 导入并执行构建 ---- from builder.scene_builder import build_level print(f"[LevelBuilder] 开始构建...") build_log = build_level(str(json_path), clear_existing=True) # ---- 保存关卡 ---- print("[LevelBuilder] 保存关卡...") try: unreal.EditorLevelLibrary.save_current_level() print(f"[LevelBuilder] ✅ 关卡已保存: {map_asset_path}") except Exception as e: print(f"[LevelBuilder] ⚠️ 保存失败: {e}") # ---- 构建结果汇总 ---- if build_log: ok = sum(1 for b in build_log if b.get("status") == "ok") fail = len(build_log) - ok print(f"") print(f"[LevelBuilder] {'=' * 40}") print(f"[LevelBuilder] SUCCESS: {ok} | FAILED: {fail} | TOTAL: {len(build_log)}") print(f"[LevelBuilder] {'=' * 40}") if fail > 0: print(f"[LevelBuilder] 失败列表:") for b in build_log: if b.get("status") != "ok": print(f" ❌ {b.get('name', '?')}: {b.get('reason', '未知')}") # ---- 退出编辑器 ---- if not no_quit: print("[LevelBuilder] 退出编辑器...") unreal.SystemLibrary.execute_console_command(None, "quit") print("[LevelBuilder] Done.")