Files
HelloVulkan/Engine/src/Serialization/SceneSerializer.cpp
T

1342 lines
61 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/10/3.
//
// SceneSerializer:场景 ↔ YAML。结构见头文件注释。
//
// 写端:手动 Emitter 逐键输出(短集合用 Flow 单行数组/内联 map,
// 浮点 9 位精度保证 float32 无损往返)。
// 读端:yaml-cpp 新版对"僵尸节点"(缺失键的返回值)硬化——operator=、
// size()、Type()、链式 operator[] 一律抛 InvalidNode,只有 IsDefined/
// as<T>(fallback)/begin() 安全。因此读侧铁律:每层节点先
// `const Node n = parent["Key"]` 拷贝初始化 + `if (n)` 判空再访问,
// 绝不对可能缺失的键做赋值或链式下标。
//
// 原子性:Deserialize 先解析到中间结构(逐实体容错),全部成功才
// scene.Clear() 换血重建——解析失败不动现场。
//
#include <Engine/Serialization/SceneSerializer.h>
#include <Engine/ECS/Scene.h>
#include <Engine/ECS/EntityHandle.h>
#include <Engine/ECS/Components.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Core/Log.h>
#include <Engine/Core/UUID.h>
#include <Engine/Scripting/Script.h>
#include <yaml-cpp/yaml.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <algorithm>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace Engine::Serialization {
namespace {
// ---- 写端辅助:Flow 单行数组(Position: [0, 1.2, -6])----
void EmitVec3(YAML::Emitter& e, const glm::vec3& v)
{
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z
<< YAML::EndSeq;
}
void EmitVec2(YAML::Emitter& e, const glm::vec2& v)
{
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << YAML::EndSeq;
}
void EmitVec4(YAML::Emitter& e, const glm::vec4& v)
{
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << v.w
<< YAML::EndSeq;
}
void EmitQuat(YAML::Emitter& e, const glm::quat& q)
{
e << YAML::Flow << YAML::BeginSeq << q.w << q.x << q.y << q.z
<< YAML::EndSeq;
}
// ---- 读端辅助:缺字段/类型错/僵尸节点 → 默认值 ----
glm::vec3 ReadVec3(const YAML::Node& n, const glm::vec3& def)
{
if (!n || !n.IsSequence() || n.size() < 3) return def;
return {n[0].as<f32>(def.x), n[1].as<f32>(def.y),
n[2].as<f32>(def.z)};
}
glm::vec2 ReadVec2(const YAML::Node& n, const glm::vec2& def)
{
if (!n || !n.IsSequence() || n.size() < 2) return def;
return {n[0].as<f32>(def.x), n[1].as<f32>(def.y)};
}
// ---- 脚本参数读端:按声明 schema 收值(调用方保证键已知)----
// 数值臂互转(YAML 标量无类型:"3" 作 f32/i32 均自然,i32 声明
// 遇浮点值截断);形不符回声明默认 + 警告(加载期一次,不刷屏)
template<typename T>
bool TryAs(const YAML::Node& n, T& out)
{
try {
out = n.as<T>();
return true;
} catch (const std::exception&) {
return false; // 类型不符 / 僵尸节点硬化异常统一兜住
}
}
Scripting::ParamValue ReadParamValue(
const YAML::Node& n, const Scripting::ParamBuilder::Desc& d)
{
switch (d.type) {
case Scripting::ParamType::F32: {
f32 v{};
if (TryAs<f32>(n, v)) return {v};
i32 iv{};
if (TryAs<i32>(n, iv)) return {static_cast<f32>(iv)};
break;
}
case Scripting::ParamType::I32: {
i32 v{};
if (TryAs<i32>(n, v)) return {v};
f32 fv{};
if (TryAs<f32>(n, fv)) return {static_cast<i32>(fv)};
break;
}
case Scripting::ParamType::Bool: {
bool v{};
if (TryAs<bool>(n, v)) return {v};
break;
}
case Scripting::ParamType::String: {
if (n.IsScalar()) return {n.Scalar()};
break;
}
case Scripting::ParamType::Vec2: {
if (n && n.IsSequence() && n.size() >= 2) {
return {ReadVec2(n, glm::vec2{0.0f})};
}
break;
}
case Scripting::ParamType::Vec3: {
if (n && n.IsSequence() && n.size() >= 3) {
return {ReadVec3(n, glm::vec3{0.0f})};
}
break;
}
}
ENGINE_WARN("Script param '{}' type mismatch, use default",
d.name);
return d.def;
}
glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def)
{
if (!n || !n.IsSequence() || n.size() < 4) return def;
return {n[0].as<f32>(def.x), n[1].as<f32>(def.y),
n[2].as<f32>(def.z), n[3].as<f32>(def.w)};
}
// [w,x,y,z] → 归一化 quat;非法/缺失回退单位四元数(防手改档出 NaN)
glm::quat ReadQuat(const YAML::Node& n)
{
if (!n || !n.IsSequence() || n.size() < 4) return {};
const f32 w = n[0].as<f32>(1.0f), x = n[1].as<f32>(0.0f);
const f32 y = n[2].as<f32>(0.0f), z = n[3].as<f32>(0.0f);
const f32 len = std::sqrt(x * x + y * y + z * z + w * w);
return len > 1e-8f ? glm::quat(w, x, y, z) / len : glm::quat{};
}
// "a,b,c[,d]" → N 个 int(procedural 伪路径参数解析)
template <size_t N>
bool ParseCsvInts(const std::string& s, int (&out)[N])
{
size_t pos = 0;
for (size_t i = 0; i < N; ++i) {
const size_t next =
(i + 1 < N) ? s.find(',', pos) : std::string::npos;
if (i + 1 < N && next == std::string::npos) return false;
try {
out[i] = std::stoi(s.substr(pos, next - pos));
}
catch (const std::exception&) { return false; }
pos = (next == std::string::npos) ? s.size() : next + 1;
}
return true;
}
// procedural:// 伪路径 → 参数反解重建(确定性键必然命中缓存;
// 绝不对伪路径调 LoadModel——那会走 assimp 必炸)
ModelRef ResolveProcedural(Assets::AssetManager& assets,
const std::string& path)
{
constexpr std::string_view kPlane = "procedural://plane/";
constexpr std::string_view kTPlane =
"procedural://transparent-plane/";
constexpr std::string_view kCube = "procedural://cube/";
if (path.rfind(kCube, 0) == 0) {
// "{halfSize}/{r},{g},{b}"
const std::string rest = path.substr(kCube.size());
const auto slash = rest.find('/');
if (slash == std::string::npos) return {};
int c[3];
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
try {
return assets.CreateCube(
std::stof(rest.substr(0, slash)),
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
static_cast<u8>(c[2]));
}
catch (const std::exception&) { return {}; }
}
if (path.rfind(kTPlane, 0) == 0) {
// "{halfSize}/{r},{g},{b},{a}"
const std::string rest = path.substr(kTPlane.size());
const auto slash = rest.find('/');
if (slash == std::string::npos) return {};
int c[4];
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
try {
return assets.CreateTransparentPlane(
std::stof(rest.substr(0, slash)),
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
static_cast<u8>(c[2]), static_cast<u8>(c[3]));
}
catch (const std::exception&) { return {}; }
}
if (path.rfind(kPlane, 0) == 0) {
// "{halfSize}/{r},{g},{b}"
const std::string rest = path.substr(kPlane.size());
const auto slash = rest.find('/');
if (slash == std::string::npos) return {};
int c[3];
if (!ParseCsvInts(rest.substr(slash + 1), c)) return {};
try {
return assets.CreatePlane(
std::stof(rest.substr(0, slash)),
static_cast<u8>(c[0]), static_cast<u8>(c[1]),
static_cast<u8>(c[2]));
}
catch (const std::exception&) { return {}; }
}
return {}; // 未知伪路径
}
// 模型引用解析降级链:procedural 反解 → GUID 主键 → Path 兜底。
// 加载成功后 GetOrCreateGUID 回写注册表(注册表丢档自愈)
ModelRef ResolveModel(Assets::AssetManager& assets, u64 guid,
const std::string& path)
{
if (ModelRef ref = ResolveProcedural(assets, path); ref.Valid()) {
return ref;
}
std::string resolved;
if (guid != 0) resolved = assets.ResolveGUID(guid);
if (resolved.empty()) resolved = path;
if (resolved.empty()) return {};
ModelRef ref = assets.LoadModel(resolved);
if (ref.Valid()) assets.GetOrCreateGUID(resolved);
return ref;
}
// 材质覆盖路径解析:guid 反查优先(资产移动不断引用),path
// 兜底;解析成功即预载——"收集期只查缓存"不变量在此保证
//(游戏形态无编辑器,场景读后预载是唯一自动加载点),未加载
// 成功也返回路径(收集期回退内建材质,不丢实体)
std::string ResolveMaterialPath(Assets::AssetManager& assets,
u64 guid, const std::string& path)
{
std::string resolved;
if (guid != 0) resolved = assets.ResolveGUID(guid);
if (resolved.empty()) resolved = path;
if (resolved.empty()) return {};
if (!assets.IsMaterialLoaded(resolved)) {
assets.LoadMaterialAsset(resolved);
}
assets.GetOrCreateGUID(resolved);
return resolved;
}
// ---- 中间结构:解析全部成功才碰 registry ----
// comps 持 Components 节点(仅在键存在且为 map 时赋值——
// yaml-cpp 新版对僵尸节点做 operator= 会抛异常)
struct ParsedEntity
{
u64 uuid = 0;
std::string name = "entity";
u64 parentUuid = 0; // 0 = 根实体(层级键缺省)
YAML::Node comps; // 缺省 = Null(无组件)
};
// 实体列表解析(场景加载 / Prefab 实例化共用):逐实体容错,
// 单实体损坏跳过继续,不动 registry
void ParseEntityList(const YAML::Node& entities,
std::vector<ParsedEntity>& out)
{
if (!entities || !entities.IsSequence()) {
return;
}
out.reserve(entities.size());
for (const auto& e : entities) {
try {
ParsedEntity pe;
pe.uuid = e["Entity"].as<u64>(0);
pe.name = e["Name"].as<std::string>(pe.name);
pe.parentUuid = e["Parent"].as<u64>(0);
if (const YAML::Node comps = e["Components"];
comps && comps.IsMap()) {
// 仅在 rhs 有效时赋值(僵尸节点 operator= 会抛)
pe.comps = comps;
}
out.push_back(std::move(pe));
}
catch (const std::exception& err) {
// 单实体损坏不炸全场:跳过继续
ENGINE_WARN("SceneSerializer: entity skipped ({})",
err.what());
}
}
}
// 写端:单实体 → Emitter(调用方已进入 Entities 序列)
void EmitEntity(YAML::Emitter& out, Assets::AssetManager& assets,
const ECS::Registry& reg, entt::entity e)
{
const auto& id = reg.get<ECS::IdComponent>(e);
out << YAML::BeginMap;
out << YAML::Key << "Entity" << YAML::Value << id.uuid.Value();
if (const auto* name = reg.try_get<ECS::NameComponent>(e)) {
out << YAML::Key << "Name" << YAML::Value << name->name;
}
// 层级(UE Actor attach):父存 UUID(跨存档稳定);
// 悬空父(不该发生)按根实体写
if (const auto* h = reg.try_get<ECS::HierarchyComponent>(e);
h && h->parent != entt::null && reg.valid(h->parent)) {
out << YAML::Key << "Parent" << YAML::Value
<< reg.get<ECS::IdComponent>(h->parent).uuid.Value();
}
out << YAML::Key << "Components" << YAML::Value << YAML::BeginMap;
if (const auto* t = reg.try_get<ECS::TransformComponent>(e)) {
out << YAML::Key << "Transform" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Position" << YAML::Value;
EmitVec3(out, t->GetPosition());
out << YAML::Key << "Rotation" << YAML::Value;
EmitQuat(out, t->GetRotationQuat()); // (w,x,y,z) 无损
out << YAML::Key << "Scale" << YAML::Value;
EmitVec3(out, t->GetScale());
out << YAML::EndMap;
}
if (const auto* mr = reg.try_get<ECS::MeshRendererComponent>(e)) {
// ModelRef → {Guid, Path}:Path 反查为空 = 无效引用,跳过
const std::string& path = assets.Models().Path(mr->model);
if (!path.empty()) {
out << YAML::Key << "MeshRenderer" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Model" << YAML::Value
<< YAML::BeginMap;
if (const u64 guid = assets.GetOrCreateGUID(path);
guid != 0) {
out << YAML::Key << "Guid" << YAML::Value << guid;
}
out << YAML::Key << "Path" << YAML::Value << path;
out << YAML::EndMap; // Model(flow)
out << YAML::Key << "Active" << YAML::Value << mr->active;
// 材质覆盖表(UE Material Slot):seq of {Slot,Guid,Path}
if (!mr->materialOverrides.empty()) {
out << YAML::Key << "MaterialOverrides"
<< YAML::Value << YAML::BeginSeq;
for (const auto& [slot, mpath] :
mr->materialOverrides) {
out << YAML::BeginMap;
out << YAML::Key << "Slot" << YAML::Value << slot;
if (const u64 guid = assets.GetOrCreateGUID(mpath);
guid != 0) {
out << YAML::Key << "Guid"
<< YAML::Value << guid;
}
out << YAML::Key << "Path"
<< YAML::Value << mpath;
out << YAML::EndMap;
}
out << YAML::EndSeq;
}
out << YAML::EndMap; // MeshRenderer
}
else {
ENGINE_WARN("SceneSerializer: entity has invalid model "
"ref, MeshRenderer skipped");
}
}
if (const auto* a = reg.try_get<ECS::AnimationComponent>(e)) {
out << YAML::Key << "Animation" << YAML::Value
<< YAML::BeginMap; // block 风格:键多,单行不可读
out << YAML::Key << "ClipIndex" << YAML::Value << a->clipIndex;
out << YAML::Key << "Playing" << YAML::Value << a->playing;
out << YAML::Key << "Loop" << YAML::Value << a->loop;
out << YAML::Key << "Speed" << YAML::Value << a->speed;
out << YAML::Key << "RootMotion" << YAML::Value
<< a->applyRootMotion;
out << YAML::Key << "FadeDuration" << YAML::Value
<< a->fadeDuration;
out << YAML::Key << "Active" << YAML::Value << a->active;
// Controller 模式(Unity Animator 语义):mode/路径/参数
// 实例值落盘;状态机播放头瞬态不落盘(重启从 entry 开始)
out << YAML::Key << "Mode" << YAML::Value
<< static_cast<int>(a->mode);
out << YAML::Key << "ControllerPath" << YAML::Value
<< a->controllerPath;
if (!a->floatParams.empty()) {
out << YAML::Key << "FloatParams" << YAML::Value
<< YAML::BeginMap;
for (const auto& [k, v] : a->floatParams) {
out << YAML::Key << k << YAML::Value << v;
}
out << YAML::EndMap;
}
if (!a->boolParams.empty()) {
out << YAML::Key << "BoolParams" << YAML::Value
<< YAML::BeginMap;
for (const auto& [k, v] : a->boolParams) {
out << YAML::Key << k << YAML::Value << v;
}
out << YAML::EndMap;
}
// 外部动画源(编辑器 attach 列表):clip 本体运行时并入
// Model 资产不落盘,加载端按此列表幂等重放。
// block 风格:路径长且条目多,flow 单行不可读
out << YAML::Key << "Attached" << YAML::Value
<< YAML::BeginSeq;
for (const auto& p : a->attachedAnimPaths) {
out << p;
}
out << YAML::EndSeq;
out << YAML::EndMap;
}
if (const auto* c = reg.try_get<ECS::CameraComponent>(e)) {
out << YAML::Key << "Attached" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Primary" << YAML::Value << c->primary;
out << YAML::Key << "Active" << YAML::Value << c->active;
// 投影参数(Camera 成员):老场景缺键时读侧回退默认值
out << YAML::Key << "ProjectionMode" << YAML::Value
<< static_cast<int>(c->camera.GetProjectionMode());
out << YAML::Key << "FovDeg" << YAML::Value
<< c->camera.GetFovDeg();
out << YAML::Key << "NearZ" << YAML::Value
<< c->camera.GetNearZ();
out << YAML::Key << "FarZ" << YAML::Value
<< c->camera.GetFarZ();
out << YAML::Key << "OrthoSize" << YAML::Value
<< c->camera.GetOrthoSize();
out << YAML::EndMap;
}
if (const auto* l = reg.try_get<ECS::LightComponent>(e)) {
out << YAML::Key << "Light" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Type" << YAML::Value
<< static_cast<int>(l->type);
out << YAML::Key << "Direction" << YAML::Value;
EmitVec3(out, l->direction);
out << YAML::Key << "Color" << YAML::Value;
EmitVec4(out, l->color);
out << YAML::Key << "Radius" << YAML::Value << l->radius;
out << YAML::Key << "CastShadow" << YAML::Value
<< l->castShadow;
out << YAML::Key << "Active" << YAML::Value << l->active;
out << YAML::EndMap;
}
if (const auto* sb = reg.try_get<ECS::SkyboxComponent>(e)) {
out << YAML::Key << "Skybox" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Active" << YAML::Value << sb->active;
out << YAML::Key << "EnvMap" << YAML::Value << sb->envPath;
out << YAML::EndMap;
}
if (const auto* sl = reg.try_get<ECS::SkyLightComponent>(e)) {
out << YAML::Key << "SkyLight" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Ambient" << YAML::Value;
EmitVec4(out, sl->ambientColor);
out << YAML::Key << "Active" << YAML::Value << sl->active;
out << YAML::EndMap;
}
if (const auto* rb = reg.try_get<ECS::RigidBodyComponent>(e)) {
out << YAML::Key << "RigidBody" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Type" << YAML::Value
<< static_cast<int>(rb->type);
out << YAML::Key << "Collider" << YAML::Value
<< static_cast<int>(rb->collider);
out << YAML::Key << "BoxHalfExtents" << YAML::Value;
EmitVec3(out, rb->boxHalfExtents);
out << YAML::Key << "SphereRadius" << YAML::Value
<< rb->sphereRadius;
out << YAML::Key << "CapsuleRadius" << YAML::Value
<< rb->capsuleRadius;
out << YAML::Key << "CapsuleHalfHeight" << YAML::Value
<< rb->capsuleHalfHeight;
out << YAML::Key << "CylinderRadius" << YAML::Value
<< rb->cylinderRadius;
out << YAML::Key << "CylinderHalfHeight" << YAML::Value
<< rb->cylinderHalfHeight;
if (!rb->compound.empty()) {
out << YAML::Key << "Compound" << YAML::Value
<< YAML::BeginSeq;
for (const auto& c : rb->compound) {
out << YAML::BeginMap;
out << YAML::Key << "Shape" << YAML::Value
<< static_cast<int>(c.shape);
out << YAML::Key << "BoxHalfExtents" << YAML::Value;
EmitVec3(out, c.boxHalfExtents);
out << YAML::Key << "Radius" << YAML::Value << c.radius;
out << YAML::Key << "CapsuleHalfHeight" << YAML::Value
<< c.capsuleHalfHeight;
out << YAML::Key << "Position" << YAML::Value;
EmitVec3(out, c.position);
out << YAML::Key << "Rotation" << YAML::Value;
EmitQuat(out, c.rotation); // (w,x,y,z)
out << YAML::EndMap;
}
out << YAML::EndSeq;
}
out << YAML::Key << "Mass" << YAML::Value << rb->mass;
out << YAML::Key << "GravityScale" << YAML::Value
<< rb->gravityScale;
out << YAML::Key << "Friction" << YAML::Value << rb->friction;
out << YAML::Key << "Restitution" << YAML::Value
<< rb->restitution;
out << YAML::Key << "IsTrigger" << YAML::Value << rb->isTrigger;
// bodyId/bodyValid/velocity 由 PhysicsWorld 对账重建,不存
out << YAML::Key << "Active" << YAML::Value << rb->active;
out << YAML::EndMap;
}
if (const auto* jc = reg.try_get<ECS::JointComponent>(e)) {
out << YAML::Key << "Joint" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Type" << YAML::Value
<< static_cast<int>(jc->type);
// 实体引用存 UUID(跨存档稳定;target=0 未设置)
out << YAML::Key << "Target" << YAML::Value
<< jc->target.Value();
out << YAML::Key << "AnchorA" << YAML::Value;
EmitVec3(out, jc->anchorA);
out << YAML::Key << "AnchorB" << YAML::Value;
EmitVec3(out, jc->anchorB);
out << YAML::Key << "HingeAxis" << YAML::Value;
EmitVec3(out, jc->hingeAxis);
out << YAML::Key << "EnableLimit" << YAML::Value
<< jc->enableLimit;
out << YAML::Key << "LimitMin" << YAML::Value << jc->limitMin;
out << YAML::Key << "LimitMax" << YAML::Value << jc->limitMax;
out << YAML::Key << "Active" << YAML::Value << jc->active;
out << YAML::EndMap;
}
// 角色只存参数(速度/着地等状态不序列化)
if (const auto* cc = reg.try_get<ECS::CharacterControllerComponent>(e)) {
out << YAML::Key << "CharacterController" << YAML::Value
<< YAML::BeginMap;
out << YAML::Key << "Radius" << YAML::Value << cc->radius;
out << YAML::Key << "CapsuleHalfHeight" << YAML::Value
<< cc->capsuleHalfHeight;
out << YAML::Key << "Mass" << YAML::Value << cc->mass;
out << YAML::Key << "MaxSlopeAngle" << YAML::Value
<< cc->maxSlopeAngle;
out << YAML::Key << "StepHeight" << YAML::Value
<< cc->stepHeight;
out << YAML::Key << "GravityScale" << YAML::Value
<< cc->gravityScale;
out << YAML::Key << "Active" << YAML::Value << cc->active;
out << YAML::EndMap;
}
// 脚本:多脚本列表(每项 = 名字 + 独立启停位);未注册名字
// 加载后运行时警告跳过
if (const auto* sc = reg.try_get<ECS::ScriptComponent>(e)) {
out << YAML::Key << "Script" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Active" << YAML::Value << sc->active;
out << YAML::Key << "Items" << YAML::Value << YAML::BeginSeq;
for (const auto& item : sc->items) {
// flow map 单行一项,与场景格式定案一致
out << YAML::BeginMap;
out << YAML::Key << "Name" << YAML::Value << item.name;
out << YAML::Key << "Active" << YAML::Value << item.active;
if (!item.params.empty()) {
out << YAML::Key << "Params" << YAML::Value
<< YAML::BeginMap;
for (const auto& p : item.params) {
switch (p.value.Type()) {
case Engine::Scripting::ParamType::F32:
out << YAML::Key << p.name << YAML::Value
<< std::get<f32>(p.value.v);
break;
case Engine::Scripting::ParamType::I32:
out << YAML::Key << p.name << YAML::Value
<< std::get<i32>(p.value.v);
break;
case Engine::Scripting::ParamType::Bool:
out << YAML::Key << p.name << YAML::Value
<< std::get<bool>(p.value.v);
break;
case Engine::Scripting::ParamType::String:
out << YAML::Key << p.name << YAML::Value
<< std::get<std::string>(p.value.v);
break;
case Engine::Scripting::ParamType::Vec2:
out << YAML::Key << p.name << YAML::Value;
EmitVec2(out, std::get<glm::vec2>(p.value.v));
break;
case Engine::Scripting::ParamType::Vec3:
out << YAML::Key << p.name << YAML::Value;
EmitVec3(out, std::get<glm::vec3>(p.value.v));
break;
}
}
out << YAML::EndMap;
}
out << YAML::EndMap;
}
out << YAML::EndSeq;
out << YAML::EndMap;
}
out << YAML::EndMap; // Components
out << YAML::EndMap; // Entity
}
// 组件应用(场景加载 / Prefab 实例化共用):Components 节点 →
// 逐组件解析建池。handle 须为 CreateEntity 出厂态(IdComponent/
// Transform 出生即有,其余组件按需 AddComponent)。资产引用
// 解析与预载语义见各 Resolve* 辅助
void ApplyComponents(ECS::EntityHandle handle,
const YAML::Node& comps,
Assets::AssetManager& assets)
{
if (const YAML::Node n = comps["Transform"]) {
auto& t = handle.GetComponent<ECS::TransformComponent>();
t.SetPosition(ReadVec3(n["Position"], t.GetPosition()));
t.SetRotationQuat(ReadQuat(n["Rotation"]));
t.SetScale(ReadVec3(n["Scale"], t.GetScale()));
}
if (const YAML::Node n = comps["MeshRenderer"]) {
if (const YAML::Node m = n["Model"]) {
const u64 guid = m["Guid"].as<u64>(0);
const auto path = m["Path"].as<std::string>("");
if (ModelRef ref = ResolveModel(assets, guid, path);
ref.Valid()) {
handle.AddComponent<ECS::MeshRendererComponent>(
ECS::MeshRendererComponent{ref});
auto& mrComp =
handle.GetComponent<ECS::MeshRendererComponent>();
mrComp.active =
n["Active"].as<bool>(true); // 缺省 true 向前兼容
// 材质覆盖表:逐项解析(guid 反查 → path 兜底)
// 并预载;加载失败保留路径(收集期回退内建材质)
if (const YAML::Node ovs = n["MaterialOverrides"]) {
for (const auto& ov : ovs) {
const u32 slot = ov["Slot"].as<u32>(
0xFFFFFFFFu);
const auto opath = ResolveMaterialPath(
assets, ov["Guid"].as<u64>(0),
ov["Path"].as<std::string>(""));
if (slot == 0xFFFFFFFFu || opath.empty()) {
continue;
}
mrComp.materialOverrides[slot] = opath;
}
}
}
else {
ENGINE_WARN("SceneSerializer: model unresolved "
"(guid={}, path='{}'), MeshRenderer "
"skipped",
guid, path);
}
}
}
if (const YAML::Node n = comps["Animation"]) {
auto& a = handle.AddComponent<ECS::AnimationComponent>();
a.clipIndex = n["ClipIndex"].as<int>(a.clipIndex);
a.playing = n["Playing"].as<bool>(a.playing);
a.loop = n["Loop"].as<bool>(a.loop);
a.speed = n["Speed"].as<f32>(a.speed);
a.applyRootMotion = n["RootMotion"].as<bool>(
a.applyRootMotion); // 缺键 = 旧行为(保留根位移)
a.fadeDuration =
n["FadeDuration"].as<f32>(a.fadeDuration); // 缺键回退 0.3
a.active = n["Active"].as<bool>(true);
// Controller 模式缺键回退 Direct(老场景零迁移);
// mode 越界值也视为 Direct
if (const int m = n["Mode"].as<int>(0); m == 1) {
a.mode = ECS::AnimationComponent::AnimMode::Controller;
}
a.controllerPath = n["ControllerPath"].as<std::string>("");
// Controller 预载(收集期只查缓存的不变量在此保证;
// 解析失败保留路径,System Find 落空 → rest 兜底)
if (!a.controllerPath.empty() &&
!assets.IsControllerLoaded(a.controllerPath)) {
assets.LoadController(a.controllerPath);
}
if (const YAML::Node fp = n["FloatParams"]) {
for (const auto& it : fp) {
a.floatParams[it.first.as<std::string>("")] =
it.second.as<float>(0.0f);
}
}
if (const YAML::Node bp = n["BoolParams"]) {
for (const auto& it : bp) {
a.boolParams[it.first.as<std::string>("")] =
it.second.as<bool>(false);
}
}
if (const YAML::Node att = n["Attached"]) {
for (const auto& it : att) {
a.attachedAnimPaths.push_back(
it.as<std::string>(""));
}
}
}
if (const YAML::Node n = comps["Camera"]) {
auto& c = handle.AddComponent<ECS::CameraComponent>();
c.primary = n["Primary"].as<bool>(false);
c.active = n["Active"].as<bool>(true);
// 投影参数缺省回退默认值(Version 向后兼容:老场景无新键
// 可载)。ProjectionMode 越界值视为透视
if (const int pm = n["ProjectionMode"].as<int>(0);
pm == 1) {
c.camera.SetProjectionMode(
Engine::Camera::ProjectionMode::Orthographic);
}
c.camera.SetFovDeg(n["FovDeg"].as<f32>(c.camera.GetFovDeg()));
c.camera.SetClip(n["NearZ"].as<f32>(c.camera.GetNearZ()),
n["FarZ"].as<f32>(c.camera.GetFarZ()));
c.camera.SetOrthoSize(
n["OrthoSize"].as<f32>(c.camera.GetOrthoSize()));
}
if (const YAML::Node n = comps["Light"]) {
auto& l = handle.AddComponent<ECS::LightComponent>();
if (const int type = n["Type"].as<int>(0); type >= 0 &&
type <= 1) {
l.type = static_cast<ECS::LightComponent::Type>(type);
}
l.direction = ReadVec3(n["Direction"], l.direction);
l.color = ReadVec4(n["Color"], l.color);
l.radius = n["Radius"].as<f32>(l.radius);
l.castShadow = n["CastShadow"].as<bool>(l.castShadow);
l.active = n["Active"].as<bool>(true);
}
if (const YAML::Node n = comps["RigidBody"]) {
auto& rb = handle.AddComponent<ECS::RigidBodyComponent>();
if (const int type = n["Type"].as<int>(0);
type >= 0 && type <= 2) {
rb.type =
static_cast<ECS::RigidBodyComponent::BodyType>(type);
}
if (const int col = n["Collider"].as<int>(0);
col >= 0 && col <= 5) {
rb.collider =
static_cast<ECS::RigidBodyComponent::ColliderType>(
col);
}
rb.boxHalfExtents = ReadVec3(n["BoxHalfExtents"],
rb.boxHalfExtents);
rb.sphereRadius = n["SphereRadius"].as<f32>(rb.sphereRadius);
rb.capsuleRadius = n["CapsuleRadius"].as<f32>(rb.capsuleRadius);
rb.capsuleHalfHeight =
n["CapsuleHalfHeight"].as<f32>(rb.capsuleHalfHeight);
rb.cylinderRadius =
n["CylinderRadius"].as<f32>(rb.cylinderRadius);
rb.cylinderHalfHeight =
n["CylinderHalfHeight"].as<f32>(rb.cylinderHalfHeight);
// 僵尸节点铁律:拷贝初始化 + 判空再遍历(缺 Compound 键安全)
if (const YAML::Node cs = n["Compound"]) {
for (const auto& item : cs) {
ECS::RigidBodyComponent::CompoundChild c;
if (const int sh = item["Shape"].as<int>(0);
sh >= 0 && sh <= 2) {
c.shape = static_cast<
ECS::RigidBodyComponent::CompoundChild::Shape>(
sh);
}
c.boxHalfExtents = ReadVec3(item["BoxHalfExtents"],
c.boxHalfExtents);
c.radius = item["Radius"].as<f32>(c.radius);
c.capsuleHalfHeight =
item["CapsuleHalfHeight"].as<f32>(
c.capsuleHalfHeight);
c.position = ReadVec3(item["Position"], c.position);
c.rotation = ReadQuat(item["Rotation"]);
rb.compound.push_back(std::move(c));
}
}
rb.mass = n["Mass"].as<f32>(rb.mass);
rb.gravityScale = n["GravityScale"].as<f32>(rb.gravityScale);
rb.friction = n["Friction"].as<f32>(rb.friction);
rb.restitution = n["Restitution"].as<f32>(rb.restitution);
// 新键 IsTrigger;旧场景 IsSensor 兼容读(缺键 as 返回 fallback 安全)
rb.isTrigger = n["IsTrigger"].as<bool>(
n["IsSensor"].as<bool>(rb.isTrigger));
rb.active = n["Active"].as<bool>(true);
// bodyId/bodyValid/velocity 保持默认(对账重建)
}
// Joint:target 只存 UUID,加载后由 PhysicsWorld 惰性反查
//(两遍解析不需要——UUID 再生/加载顺序均由逐帧解析消化)
if (const YAML::Node n = comps["Joint"]) {
auto& jc = handle.AddComponent<ECS::JointComponent>();
if (const int type = n["Type"].as<int>(0);
type >= 0 && type <= 2) {
jc.type =
static_cast<ECS::JointComponent::Type>(type);
}
jc.target = UUID{n["Target"].as<u64>(0)};
jc.anchorA = ReadVec3(n["AnchorA"], jc.anchorA);
jc.anchorB = ReadVec3(n["AnchorB"], jc.anchorB);
jc.hingeAxis = ReadVec3(n["HingeAxis"], jc.hingeAxis);
jc.enableLimit = n["EnableLimit"].as<bool>(jc.enableLimit);
jc.limitMin = n["LimitMin"].as<f32>(jc.limitMin);
jc.limitMax = n["LimitMax"].as<f32>(jc.limitMax);
jc.active = n["Active"].as<bool>(true);
}
if (const YAML::Node n = comps["CharacterController"]) {
auto& cc =
handle.AddComponent<ECS::CharacterControllerComponent>();
cc.radius = n["Radius"].as<f32>(cc.radius);
cc.capsuleHalfHeight =
n["CapsuleHalfHeight"].as<f32>(cc.capsuleHalfHeight);
cc.mass = n["Mass"].as<f32>(cc.mass);
cc.maxSlopeAngle = n["MaxSlopeAngle"].as<f32>(cc.maxSlopeAngle);
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
cc.gravityScale = n["GravityScale"].as<f32>(cc.gravityScale);
cc.active = n["Active"].as<bool>(true);
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
}
// 脚本:优先读多脚本 Items;无 Items 回退旧单脚本键(向前兼容)
if (const YAML::Node n = comps["Script"]) {
auto& sc = handle.AddComponent<ECS::ScriptComponent>();
sc.active = n["Active"].as<bool>(true);
if (const YAML::Node items = n["Items"]) {
for (const auto& it : items) {
ECS::ScriptComponent::Item item;
item.name = it["Name"].as<std::string>("");
item.active = it["Active"].as<bool>(true);
// 参数:按脚本 schema 对齐收值——未知键忽略(前向
// 兼容),缺失键不存(运行时用声明默认)
const YAML::Node ps = it["Params"];
if (ps) {
const auto& schema =
Scripting::ScriptRegistry::Schema(item.name);
for (const auto& kv : ps) {
const std::string pname =
kv.first.as<std::string>("");
const auto dit = std::find_if(
schema.begin(), schema.end(),
[&](const Scripting::ParamBuilder::Desc& d) {
return d.name == pname;
});
if (dit == schema.end()) {
continue;
}
item.params.push_back(
{pname, ReadParamValue(kv.second, *dit)});
}
}
// 去重兜底:同名项只收第一份(UI 已杜绝重复,
// 手编场景仍可能写出重复)
const bool dup = std::any_of(
sc.items.begin(), sc.items.end(),
[&](const ECS::ScriptComponent::Item& e) {
return e.name == item.name;
});
if (!dup) {
sc.items.push_back(std::move(item));
}
}
} else {
// 旧格式:Script: {ScriptName: ..., Active: ...}
ECS::ScriptComponent::Item item;
item.name = n["ScriptName"].as<std::string>("");
if (!item.name.empty()) {
sc.items.push_back(std::move(item));
}
}
}
if (const YAML::Node n = comps["Skybox"]) {
auto& sb = handle.AddComponent<ECS::SkyboxComponent>();
sb.active = n["Active"].as<bool>(true);
sb.envPath = n["EnvMap"].as<std::string>("");
}
if (const YAML::Node n = comps["SkyLight"]) {
auto& sl = handle.AddComponent<ECS::SkyLightComponent>();
sl.ambientColor = ReadVec4(n["Ambient"], sl.ambientColor);
sl.active = n["Active"].as<bool>(true);
}
}
// 父子重链(场景加载 / Prefab 实例化共用;两遍解析:父可能
// 出现在子之后)。keepWorld=false:local TRS 已从文件就位,
// 只建链标脏。防环由 SetParent 兜底(手编档成环 → 告警 + 挂根)。
// byUuid = 创建期构建的最终 uuid → entity 映射
void RelinkParents(
ECS::Scene& scene, const std::vector<ParsedEntity>& parsed,
const std::unordered_map<u64, entt::entity>& byUuid)
{
for (const ParsedEntity& pe : parsed) {
if (pe.parentUuid == 0) continue;
const auto c = byUuid.find(pe.uuid);
const auto p = byUuid.find(pe.parentUuid);
if (c == byUuid.end() || p == byUuid.end()) {
ENGINE_WARN("SceneSerializer: parent {} missing for "
"entity {}, kept as root",
pe.parentUuid, pe.uuid);
continue;
}
scene.SetParent(c->second, p->second, /*keepWorld=*/false);
}
}
// 子树收集(Prefab 写端):DFS 根 → 后代(父先于子,可读性 +
// 实例化"首实体 = 根"约定)
void CollectSubtree(const ECS::Registry& reg, ECS::Entity root,
std::vector<ECS::Entity>& out)
{
out.push_back(root);
if (const auto* h = reg.try_get<ECS::HierarchyComponent>(root)) {
for (const ECS::Entity c : h->children) {
if (reg.valid(c)) {
CollectSubtree(reg, c, out);
}
}
}
}
} // namespace
// ---- 保存 ----
// Emitter 构建段(文件版与内存版共用):documentName 空则不写
// 文档头 "Scene" 字段(快照无文件名,该字段是注释性的)
static std::string SerializeDoc(const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& documentName)
{
// float32 无损往返需 9 位有效数字(std::numeric_limits
// <float>::max_digits10)
YAML::Emitter out;
out.SetFloatPrecision(9);
out << YAML::BeginMap;
if (!documentName.empty()) {
out << YAML::Key << "Scene" << YAML::Value << documentName;
}
out << YAML::Key << "Version" << YAML::Value << kSceneVersion;
const auto& s = scene.GetSettings();
out << YAML::Key << "Settings" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "ClearColor" << YAML::Value;
EmitVec4(out, s.ClearColor);
out << YAML::EndMap;
out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
const ECS::Registry& reg = scene.GetRegistry();
reg.view<ECS::IdComponent>().each(
[&](entt::entity e, const ECS::IdComponent&) {
EmitEntity(out, assets, reg, e);
});
out << YAML::EndSeq;
out << YAML::EndMap;
if (!out.good()) {
ENGINE_ERROR("SceneSerializer: emit failed: {}", out.GetLastError());
return {};
}
return {out.c_str(), out.size()};
}
bool SceneSerializer::Serialize(const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& outPath)
{
try {
const std::string doc = SerializeDoc(
scene, assets,
std::filesystem::path(outPath).stem().string());
if (doc.empty()) {
return false; // emit 失败,日志已在 SerializeDoc 打过
}
std::ofstream file(outPath, std::ios::trunc);
if (!file) {
ENGINE_ERROR("SceneSerializer: cannot open '{}' for write",
outPath);
return false;
}
file << doc;
assets.FlushGUIDRegistry();
return true;
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: save failed ({})", e.what());
return false;
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: save failed ({})", e.what());
return false;
}
}
std::string SceneSerializer::SerializeToString(
const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& documentName)
{
try {
return SerializeDoc(scene, assets, documentName);
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
return {};
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: snapshot failed ({})", e.what());
return {};
}
}
// ---- 加载 ----
// 解析段(文件版与内存快照共用):root → 原子换血重建。
// sourceName 仅用于日志(文件路径或 "snapshot")
static bool ParseRoot(ECS::Scene& scene,
Assets::AssetManager& assets,
const YAML::Node& root,
const std::string& sourceName)
{
// Version 闸:高版本拒载(结构未知),缺失按 0 宽容旧档
if (const int version = root["Version"].as<int>(0);
version > kSceneVersion) {
ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}",
sourceName, version, kSceneVersion);
return false;
}
// ---- 旧档 v1 环境语义兼容读(v2 起环境 = 实体组件)----
// 僵尸节点安全:as<T>(fallback) 对缺失键返回 fallback;环境
// 键仅在真实存在时置位(避免新档误触发兼容建实体)
const std::string legacyEnv =
root["Environment"].as<std::string>("");
bool legacyEnvKey = !legacyEnv.empty();
glm::vec4 legacyAmbient{0.07f, 0.07f, 0.09f, 0.0f};
bool legacyAmbientKey = false;
bool legacyDrawSkybox = true;
bool legacySkyboxKey = false;
if (const YAML::Node st = root["Settings"]; st && st.IsMap()) {
auto& s = scene.GetSettings();
s.ClearColor = ReadVec4(st["ClearColor"], s.ClearColor);
if (const YAML::Node amb = st["AmbientColor"]) {
legacyAmbient = ReadVec4(amb, legacyAmbient);
legacyAmbientKey = true;
}
if (const YAML::Node ds = st["DrawSkybox"]) {
legacyDrawSkybox = ds.as<bool>(legacyDrawSkybox);
legacySkyboxKey = true;
}
}
// ---- 阶段 1:解析到中间结构(逐实体容错,不动 registry)----
std::vector<ParsedEntity> parsed;
ParseEntityList(root["Entities"], parsed);
// ---- 阶段 2:原子换血重建 ----
scene.Clear();
std::unordered_set<u64> seenUuids;
std::unordered_map<u64, entt::entity> byUuid; // 第二遍重链用
for (ParsedEntity& pe : parsed) {
auto handle = scene.CreateEntity(pe.name);
// UUID 重复/无效 → 重新生成(Prefab 实例化的 ID 重映射语义)
u64 uuid = pe.uuid;
if (uuid == 0 || !seenUuids.insert(uuid).second) {
uuid = UUID::New().Value();
}
handle.GetComponent<ECS::IdComponent>().uuid = UUID{uuid};
pe.uuid = uuid; // 回写最终 uuid(重生成后父引用仍可命中)
byUuid.emplace(uuid, static_cast<entt::entity>(handle));
ApplyComponents(handle, pe.comps, assets);
}
// ---- 旧档环境兼容迁移:v1 的 Environment/Settings.AmbientColor/
// DrawSkybox → 补建一个 Environment 实体(视觉等价)。仅当旧键
// 真实存在且实体列表没有环境组件时触发(新档缺省键不触发;
// 快照恢复同理——快照里组件已实体化,不走这里)
const bool legacyEnvPresent =
legacyEnvKey || legacyAmbientKey || legacySkyboxKey;
const bool hasEnvComponents =
scene.GetRegistry().view<ECS::SkyboxComponent>().size() > 0 ||
scene.GetRegistry().view<ECS::SkyLightComponent>().size() > 0;
if (legacyEnvPresent && !hasEnvComponents) {
auto env = scene.CreateEntity("Environment");
auto& sb = env.AddComponent<ECS::SkyboxComponent>();
sb.envPath = legacyEnv;
sb.active = legacyDrawSkybox;
auto& sl = env.AddComponent<ECS::SkyLightComponent>();
sl.ambientColor = legacyAmbient;
ENGINE_INFO("SceneSerializer: legacy environment migrated to "
"'Environment' entity (env='{}')",
legacyEnv);
}
// ---- 阶段 3:重链父子(两遍解析:父可能出现在子之后)----
RelinkParents(scene, parsed, byUuid);
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
// 显式补算,否则首帧 World = 单位阵
scene.UpdateTransforms();
// 序列化加载绕过了 CreateEntity 的 UUID 增量维护(565 行直接覆盖
// IdComponent.uuid),全量重建一次索引,否则 FindEntityByUUID 会全失效
scene.RebuildUuidIndex();
ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'",
parsed.size(), sourceName);
return true;
}
bool SceneSerializer::Deserialize(ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& inPath)
{
YAML::Node root;
try {
root = YAML::LoadFile(inPath);
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: cannot parse '{}' ({})", inPath,
e.what());
return false;
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: cannot read '{}' ({})", inPath,
e.what());
return false;
}
return ParseRoot(scene, assets, root, inPath);
}
bool SceneSerializer::DeserializeFromString(
ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& yamlText)
{
YAML::Node root;
try {
root = YAML::Load(yamlText);
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: snapshot parse failed ({})",
e.what());
return false;
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: snapshot parse failed ({})",
e.what());
return false;
}
return ParseRoot(scene, assets, root, "snapshot");
}
// ---- Prefab(实体子树存取;与场景共用实体段读写)----
bool SceneSerializer::SerializePrefab(const ECS::Scene& scene,
Assets::AssetManager& assets,
ECS::Entity root,
const std::string& outPath)
{
const ECS::Registry& reg = scene.GetRegistry();
if (!scene.IsAlive(root)) {
ENGINE_ERROR("SceneSerializer: prefab root invalid, '{}' not "
"written", outPath);
return false;
}
try {
YAML::Emitter out;
out.SetFloatPrecision(9);
out << YAML::BeginMap;
out << YAML::Key << "Prefab" << YAML::Value
<< std::filesystem::path(outPath).stem().string();
out << YAML::Key << "Version" << YAML::Value << kSceneVersion;
out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
std::vector<ECS::Entity> subtree;
CollectSubtree(reg, root, subtree);
for (const ECS::Entity e : subtree) {
EmitEntity(out, assets, reg, e);
}
out << YAML::EndSeq;
out << YAML::EndMap;
if (!out.good()) {
ENGINE_ERROR("SceneSerializer: prefab emit failed: {}",
out.GetLastError());
return false;
}
std::ofstream file(outPath, std::ios::trunc);
if (!file) {
ENGINE_ERROR("SceneSerializer: cannot open '{}' for write",
outPath);
return false;
}
file.write(out.c_str(), static_cast<std::streamsize>(out.size()));
assets.FlushGUIDRegistry();
return true;
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: prefab save failed ({})", e.what());
return false;
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: prefab save failed ({})", e.what());
return false;
}
}
ECS::Entity SceneSerializer::InstantiatePrefab(
ECS::Scene& scene, Assets::AssetManager& assets,
const std::string& inPath,
const glm::vec3* overridePos, const glm::quat* overrideRot)
{
YAML::Node root;
try {
root = YAML::LoadFile(inPath);
}
catch (const YAML::Exception& e) {
ENGINE_ERROR("SceneSerializer: cannot parse prefab '{}' ({})",
inPath, e.what());
return entt::null;
}
catch (const std::exception& e) {
ENGINE_ERROR("SceneSerializer: cannot read prefab '{}' ({})",
inPath, e.what());
return entt::null;
}
// 版本闸同场景(实体段同构:高版本拒载,缺失按 0 宽容)
if (const int version = root["Version"].as<int>(0);
version > kSceneVersion) {
ENGINE_ERROR("SceneSerializer: prefab '{}' version {} > "
"supported {}", inPath, version, kSceneVersion);
return entt::null;
}
// ---- 解析到中间结构(逐实体容错,同场景加载)----
std::vector<ParsedEntity> parsed;
ParseEntityList(root["Entities"], parsed);
if (parsed.empty()) {
ENGINE_ERROR("SceneSerializer: prefab '{}' has no entities",
inPath);
return entt::null;
}
// ---- 追加式实例化(不换血)。UUID 全量再生:同一 prefab 多次
// 实例化、与场景既有实体均天然不冲突。old2new 供子树内引用
// (Joint.target)重映射;父引用由 RelinkParents 走 byUuid 消化
std::unordered_map<u64, entt::entity> byUuid;
std::unordered_map<u64, u64> old2new;
ECS::Entity rootEntity = entt::null; // 约定 = 文件首实体
for (ParsedEntity& pe : parsed) {
auto handle = scene.CreateEntity(pe.name);
if (rootEntity == entt::null) {
rootEntity = handle;
}
const u64 uuid = UUID::New().Value();
old2new.emplace(pe.uuid, uuid);
pe.uuid = uuid;
handle.GetComponent<ECS::IdComponent>().uuid = UUID{uuid};
byUuid.emplace(uuid, static_cast<entt::entity>(handle));
ApplyComponents(handle, pe.comps, assets);
}
// 子树内 Joint.target 重映射(旧 → 新);指向子树外的引用保持
// 原值(悬空语义与场景加载一致,PhysicsWorld 惰性反查落空)
auto& reg = scene.GetRegistry();
for (const ParsedEntity& pe : parsed) {
const auto self = byUuid.find(pe.uuid);
if (self == byUuid.end()) {
continue;
}
if (auto* jc = reg.try_get<ECS::JointComponent>(self->second)) {
if (const auto it = old2new.find(jc->target.Value());
it != old2new.end()) {
jc->target = UUID{it->second};
}
}
}
RelinkParents(scene, parsed, byUuid);
// 根位姿覆写(local 语义:实例根挂场景根时即世界位姿)
if (rootEntity != entt::null && (overridePos || overrideRot)) {
auto& t = reg.get<ECS::TransformComponent>(rootEntity);
if (overridePos) {
t.SetPosition(*overridePos);
}
if (overrideRot) {
t.SetRotationQuat(*overrideRot);
}
}
// 实例化常发生在 OnImGuiRender/脚本回调(错过本帧
// UpdateTransforms):显式补算 world;uuid 被 CreateEntity 后
// 覆写,全量重建索引(同场景加载语义)
scene.UpdateTransforms();
scene.RebuildUuidIndex();
ENGINE_INFO("SceneSerializer: prefab '{}' instantiated "
"({} entities)", inPath, parsed.size());
return rootEntity;
}
} // namespace Engine::Serialization