a3c38aa92a
- asset_mapping: 38种BSP资产实测锚点+pivot_offset_xy自动补偿 - scene_builder: Rotator改关键字参数修正旋转轴映射 - scene_builder: spawn前XY锚点补偿(支持Yaw旋转) - scene_builder: 新增ExponentialHeightFog+大气效果后处理 - build_cmdline: 构建后自动添加雾+GameMode+DirectionalLight大气 - build_guide_parser: 去重+全局地面+旋转列名兼容+边界段范围修正 - BarrierWall实际尺寸修正(60×210非20×100) - 搭建指南: 全局地面中心坐标+西边界旋转统一+院门改双开门
208 lines
7.0 KiB
Python
208 lines
7.0 KiB
Python
"""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, setup_post_build
|
||
|
||
print(f"[LevelBuilder] 开始构建...")
|
||
build_log = build_level(str(json_path), clear_existing=True)
|
||
|
||
# ---- 后处理:GameMode + 大气 + ExponentialHeightFog ----
|
||
print("[LevelBuilder] 执行关卡后处理...")
|
||
try:
|
||
# 添加 ExponentialHeightFog(如果场景中不存在)
|
||
all_actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
||
has_fog = any("ExponentialHeightFog" in a.get_class().get_name() for a in all_actors)
|
||
if not has_fog:
|
||
editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
||
fog_actor = editor_subsystem.spawn_actor_from_class(
|
||
unreal.ExponentialHeightFog,
|
||
unreal.Vector(0, 0, 0)
|
||
)
|
||
if fog_actor:
|
||
fog_actor.set_actor_label("LVL_Global_ExpHeightFog")
|
||
fog_actor.set_folder_path("LVL_SuburbanV01/Global/Lighting")
|
||
fog_comp = fog_actor.get_component_by_class(unreal.ExponentialHeightFogComponent)
|
||
if fog_comp:
|
||
fog_comp.set_editor_property("fog_density", 0.015)
|
||
print("[LevelBuilder] ✅ ExponentialHeightFog 已添加")
|
||
except Exception as e:
|
||
print(f"[LevelBuilder] ⚠️ ExponentialHeightFog 添加失败: {e}")
|
||
|
||
setup_post_build()
|
||
|
||
|
||
# ---- 保存关卡 ----
|
||
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.")
|