Files
HelloVulkan/Engine/src/Serialization/SceneSerializer.cpp
T
atdunbg 29c413579d refactor(physics): rename isSensor to isTrigger (Unity/UE terminology)
UE calls it Trigger (Overlap Events / Trigger preset) and Unity's UI
says "Is Trigger"; "sensor" is the physics-backend term (Jolt sensor,
Box2D sensor). Rename the component/record flag and the Inspector
label. Scene files now persist "IsTrigger" with backward-compatible
read of the old "IsSensor" key.
2026-10-03 18:53:38 +08:00

676 lines
30 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 <yaml-cpp/yaml.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#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 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::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/";
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;
}
// ---- 中间结构:解析全部成功才碰 registry ----
// comps 持 Components 节点(仅在键存在且为 map 时赋值——
// yaml-cpp 新版对僵尸节点做 operator= 会抛异常)
struct ParsedEntity
{
u64 uuid = 0;
std::string name = "entity";
YAML::Node comps; // 缺省 = Null(无组件)
};
// 写端:单实体 → 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;
}
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::Flow
<< 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;
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::Flow
<< YAML::BeginMap;
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 << "Active" << YAML::Value << a->active;
out << YAML::EndMap;
}
if (const auto* c = reg.try_get<ECS::CameraComponent>(e)) {
out << YAML::Key << "Camera" << YAML::Value << YAML::Flow
<< YAML::BeginMap;
out << YAML::Key << "Primary" << YAML::Value << c->primary;
out << YAML::Key << "Active" << YAML::Value << c->active;
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* 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 << "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 << "ScriptName" << YAML::Value
<< sc->scriptName;
out << YAML::Key << "Active" << YAML::Value << sc->active;
out << YAML::EndMap;
}
out << YAML::EndMap; // Components
out << YAML::EndMap; // Entity
}
} // namespace
// ---- 保存 ----
bool SceneSerializer::Serialize(const ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& outPath,
const std::string& environmentPath)
{
try {
// float32 无损往返需 9 位有效数字(std::numeric_limits
// <float>::max_digits10)
YAML::Emitter out;
out.SetFloatPrecision(9);
out << YAML::BeginMap;
out << YAML::Key << "Scene" << YAML::Value
<< std::filesystem::path(outPath).stem().string();
out << YAML::Key << "Version" << YAML::Value << kSceneVersion;
if (!environmentPath.empty()) {
out << YAML::Key << "Environment" << YAML::Value
<< environmentPath;
}
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::Key << "AmbientColor" << YAML::Value;
EmitVec4(out, s.AmbientColor);
out << YAML::Key << "DrawSkybox" << YAML::Value << s.DrawSkybox;
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 false;
}
std::ofstream file(outPath, std::ios::trunc);
if (!file) {
ENGINE_ERROR("SceneSerializer: cannot open '{}' for write",
outPath);
return false;
}
file << out.c_str();
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;
}
}
// ---- 加载 ----
bool SceneSerializer::Deserialize(ECS::Scene& scene,
Assets::AssetManager& assets,
const std::string& inPath,
std::string& outEnvironmentPath)
{
outEnvironmentPath.clear();
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;
}
// Version 闸:高版本拒载(结构未知),缺失按 0 宽容旧档
if (const int version = root["Version"].as<int>(0);
version > kSceneVersion) {
ENGINE_ERROR("SceneSerializer: '{}' version {} > supported {}",
inPath, version, kSceneVersion);
return false;
}
// 僵尸节点安全:as<T>(fallback) 对缺失键返回 fallback
outEnvironmentPath = root["Environment"].as<std::string>("");
if (const YAML::Node st = root["Settings"]; st && st.IsMap()) {
auto& s = scene.GetSettings();
s.ClearColor = ReadVec4(st["ClearColor"], s.ClearColor);
s.AmbientColor = ReadVec4(st["AmbientColor"], s.AmbientColor);
s.DrawSkybox = st["DrawSkybox"].as<bool>(s.DrawSkybox);
}
// ---- 阶段 1:解析到中间结构(逐实体容错,不动 registry)----
std::vector<ParsedEntity> parsed;
const YAML::Node entities = root["Entities"];
if (entities && entities.IsSequence()) {
parsed.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);
if (const YAML::Node comps = e["Components"];
comps && comps.IsMap()) {
// 仅在 rhs 有效时赋值(僵尸节点 operator= 会抛)
pe.comps = comps;
}
parsed.push_back(std::move(pe));
}
catch (const std::exception& err) {
// 单实体损坏不炸全场:跳过继续
ENGINE_WARN("SceneSerializer: entity skipped ({})",
err.what());
}
}
}
// ---- 阶段 2:原子换血重建 ----
scene.Clear();
std::unordered_set<u64> seenUuids;
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};
const YAML::Node comps = pe.comps; // 拷贝初始化,缺省 Null
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});
handle.GetComponent<ECS::MeshRendererComponent>().active =
n["Active"].as<bool>(true); // 缺省 true 向前兼容
}
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.active = n["Active"].as<bool>(true);
}
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);
}
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.active = n["Active"].as<bool>(true);
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
}
// 脚本:只存类名;未注册名字由 ScriptRuntime 运行时警告跳过
if (const YAML::Node n = comps["Script"]) {
auto& sc = handle.AddComponent<ECS::ScriptComponent>();
sc.scriptName = n["ScriptName"].as<std::string>("");
sc.active = n["Active"].as<bool>(true);
}
}
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
// 显式补算,否则首帧 World = 单位阵
scene.UpdateTransforms();
ENGINE_INFO("SceneSerializer: loaded {} entities from '{}'",
parsed.size(), inPath);
return true;
}
} // namespace Engine::Serialization