"""UE5 场景构建器。 在 UE5 编辑器的 Python 环境中运行。读取 JSON 中间数据, 使用 unreal Python API 创建 Actor、设置 Transform/材质/Outliner 文件夹。 使用方式: 在 UE5 编辑器 Output Log 中切换到 Python,执行: exec(open(r"path/to/scene_builder.py").read()) build_level(r"path/to/level_v01.json") """ import json import logging import sys from pathlib import Path # 尝试导入 unreal,失败时给出提示 try: import unreal HAS_UNREAL = True except ImportError: HAS_UNREAL = False # 将项目根目录加入 sys.path 以支持 config 导入 _SCRIPT_DIR = Path(__file__).resolve().parent _TOOLS_DIR = _SCRIPT_DIR.parent.parent.parent if str(_TOOLS_DIR) not in sys.path: sys.path.insert(0, str(_TOOLS_DIR)) from tools.level_builder.config.asset_mapping import MESH_ASSETS from tools.level_builder.config.material_mapping import MATERIAL_PATHS logger = logging.getLogger("level_builder.builder") def build_level(json_path: str, clear_existing: bool = True, zones: list[str] = None): """从 JSON 数据构建 UE5 关卡。 Args: json_path: 中间 JSON 文件路径。 clear_existing: 是否清除已有的 LVL_* Actor。 zones: 仅构建指定区域(如 ["Z1", "Z3"]),None 表示全部。 """ if not HAS_UNREAL: print("[错误] 未检测到 unreal 模块。此脚本必须在 UE5 编辑器 Python 环境中运行。") return # 加载数据 data = _load_json(json_path) if not data: return actors = data.get("actors", []) print(f"[LevelBuilder] 加载 {len(actors)} 个 Actor 定义") # 可选:清除已有 LVL_* Actor if clear_existing: _clear_existing_actors() # 过滤区域 if zones: actors = [a for a in actors if a.get("zone_id") in zones or a.get("zone_id") == "GLOBAL"] print(f"[LevelBuilder] 过滤为 {len(actors)} 个 Actor(区域: {zones})") # 获取编辑器子系统 editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) if not editor_subsystem: print("[错误] 无法获取 EditorActorSubsystem") return # 统计 success = 0 failed = 0 build_log = [] for i, actor_def in enumerate(actors): name = actor_def.get("actor_name", f"Actor_{i}") asset_type = actor_def.get("asset_type", "static_mesh") try: if asset_type == "player_start": actor = _spawn_player_start(editor_subsystem, actor_def) elif asset_type == "directional_light": actor = _spawn_directional_light(editor_subsystem, actor_def) elif asset_type == "sky_light": actor = _spawn_sky_light(editor_subsystem, actor_def) elif asset_type == "point_light": actor = _spawn_point_light(editor_subsystem, actor_def) else: actor = _spawn_mesh_actor(editor_subsystem, actor_def) if actor: success += 1 build_log.append({"name": name, "status": "ok", **actor_def}) else: failed += 1 build_log.append({"name": name, "status": "failed", "reason": "spawn returned None"}) except Exception as e: failed += 1 error_msg = str(e) build_log.append({"name": name, "status": "error", "reason": error_msg}) print(f" [错误] {name}: {error_msg}") # 进度 if (i + 1) % 20 == 0: print(f" 进度: {i + 1}/{len(actors)} ({success} 成功, {failed} 失败)") print(f"\n[LevelBuilder] 构建完成!") print(f" 成功: {success}") print(f" 失败: {failed}") print(f" 总计: {len(actors)}") # 保存构建日志 log_path = Path(json_path).parent / "build_log.json" with open(log_path, "w", encoding="utf-8") as f: json.dump(build_log, f, ensure_ascii=False, indent=2) print(f" 日志: {log_path}") return build_log def _load_json(path: str) -> dict: """加载 JSON 文件。""" try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print(f"[错误] 无法加载 JSON: {e}") return None def _clear_existing_actors(): """清除场景中所有 LVL_ 前缀的 Actor。""" editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) all_actors = unreal.EditorLevelLibrary.get_all_level_actors() lvl_actors = [a for a in all_actors if a.get_actor_label().startswith("LVL_")] if lvl_actors: print(f"[LevelBuilder] 清除 {len(lvl_actors)} 个已有 LVL_* Actor...") for a in lvl_actors: unreal.EditorLevelLibrary.destroy_actor(a) else: print("[LevelBuilder] 无已有 LVL_* Actor 需清除") def _spawn_mesh_actor(editor_subsystem, actor_def: dict): """生成 StaticMesh Actor。""" asset_key = actor_def.get("asset_key", "") if not asset_key or asset_key not in MESH_ASSETS: print(f" [警告] {actor_def['actor_name']}: 资产 '{asset_key}' 未找到,使用默认 Cube") asset_key = "Cube" mesh_path = MESH_ASSETS[asset_key]["path"] static_mesh = unreal.load_asset(mesh_path) if not static_mesh: print(f" [警告] {actor_def['actor_name']}: 无法加载资产 '{mesh_path}'") return None # 生成 loc = actor_def.get("location", [0, 0, 0]) actor = editor_subsystem.spawn_actor_from_object( static_mesh, unreal.Vector(loc[0], loc[1], loc[2]) ) if not actor: return None # 设置 Transform rot = actor_def.get("rotation", [0, 0, 0]) actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2])) scale = actor_def.get("scale", [1, 1, 1]) actor.set_actor_scale3d(unreal.Vector(scale[0], scale[1], scale[2])) # 设置材质 material_name = actor_def.get("material", "") if material_name and material_name in MATERIAL_PATHS: mat_path = MATERIAL_PATHS[material_name] material = unreal.load_asset(mat_path) if material: mesh_comp = actor.get_component_by_class(unreal.StaticMeshComponent) if mesh_comp: mesh_comp.set_material(0, material) # 设置名称和文件夹 actor.set_actor_label(actor_def["actor_name"]) folder = actor_def.get("folder", "") if folder: actor.set_folder_path(folder) return actor def _spawn_player_start(editor_subsystem, actor_def: dict): """生成 PlayerStart Actor。""" loc = actor_def.get("location", [0, 0, 0]) actor = editor_subsystem.spawn_actor_from_class( unreal.PlayerStart, unreal.Vector(loc[0], loc[1], loc[2]) ) if actor: rot = actor_def.get("rotation", [0, 0, 0]) actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2])) actor.set_actor_label(actor_def["actor_name"]) folder = actor_def.get("folder", "") if folder: actor.set_folder_path(folder) return actor def _spawn_directional_light(editor_subsystem, actor_def: dict): """生成 DirectionalLight Actor。""" loc = actor_def.get("location", [0, 0, 500]) actor = editor_subsystem.spawn_actor_from_class( unreal.DirectionalLight, unreal.Vector(loc[0], loc[1], loc[2]) ) if actor: rot = actor_def.get("rotation", [-45, 315, 0]) actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2])) actor.set_actor_label(actor_def["actor_name"]) folder = actor_def.get("folder", "") if folder: actor.set_folder_path(folder) return actor def _spawn_sky_light(editor_subsystem, actor_def: dict): """生成 SkyLight Actor。""" loc = actor_def.get("location", [0, 0, 0]) actor = editor_subsystem.spawn_actor_from_class( unreal.SkyLight, unreal.Vector(loc[0], loc[1], loc[2]) ) if actor: actor.set_actor_label(actor_def["actor_name"]) folder = actor_def.get("folder", "") if folder: actor.set_folder_path(folder) return actor def _spawn_point_light(editor_subsystem, actor_def: dict): """生成 PointLight Actor。""" loc = actor_def.get("location", [0, 0, 0]) actor = editor_subsystem.spawn_actor_from_class( unreal.PointLight, unreal.Vector(loc[0], loc[1], loc[2]) ) if actor: actor.set_actor_label(actor_def["actor_name"]) folder = actor_def.get("folder", "") if folder: actor.set_folder_path(folder) return actor