新增 Prefab 系统:实体子树存取 + 编辑器 Save As Prefab/拖入实例化 + 脚本 Instantiate
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include <Engine/ECS/EntityHandle.h>
|
||||
#include <Engine/Physics/PhysicsEvents.h>
|
||||
#include <Engine/Physics/PhysicsWorld.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -32,6 +33,15 @@ namespace Engine {
|
||||
App().GetScene().DestroyEntity(e);
|
||||
}
|
||||
|
||||
// Prefab 实例化(声明在 Script.h,实现下沉此处——同 Spawn/
|
||||
// Destroy 模式;序列化器与 Application 门面都在引擎内可达)
|
||||
ECS::Entity ScriptBehavior::Instantiate(const std::string& prefabPath,
|
||||
const glm::vec3& pos,
|
||||
const glm::quat& rot) {
|
||||
return Serialization::SceneSerializer::InstantiatePrefab(
|
||||
App().GetScene(), App().GetAssets(), prefabPath, &pos, &rot);
|
||||
}
|
||||
|
||||
// ---- 参数按名读写(ScriptBehavior 声明,经绑定表;写穿
|
||||
// Item.params 由运行时 SetParam 完成)----
|
||||
bool ScriptBehavior::WriteParam(const std::string& name,
|
||||
|
||||
@@ -271,6 +271,36 @@ namespace Engine::Serialization {
|
||||
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)
|
||||
@@ -310,8 +340,8 @@ namespace Engine::Serialization {
|
||||
if (!path.empty()) {
|
||||
out << YAML::Key << "MeshRenderer" << YAML::Value
|
||||
<< YAML::BeginMap;
|
||||
out << YAML::Key << "Model" << YAML::Value << YAML::Flow
|
||||
<< 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;
|
||||
@@ -347,12 +377,14 @@ namespace Engine::Serialization {
|
||||
}
|
||||
|
||||
if (const auto* a = reg.try_get<ECS::AnimationComponent>(e)) {
|
||||
out << YAML::Key << "Animation" << YAML::Value << YAML::Flow
|
||||
<< YAML::BeginMap;
|
||||
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;
|
||||
@@ -379,8 +411,9 @@ namespace Engine::Serialization {
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
// 外部动画源(编辑器 attach 列表):clip 本体运行时并入
|
||||
// Model 资产不落盘,加载端按此列表幂等重放
|
||||
out << YAML::Key << "Attached" << YAML::Value << YAML::Flow
|
||||
// Model 资产不落盘,加载端按此列表幂等重放。
|
||||
// block 风格:路径长且条目多,flow 单行不可读
|
||||
out << YAML::Key << "Attached" << YAML::Value
|
||||
<< YAML::BeginSeq;
|
||||
for (const auto& p : a->attachedAnimPaths) {
|
||||
out << p;
|
||||
@@ -390,7 +423,7 @@ namespace Engine::Serialization {
|
||||
}
|
||||
|
||||
if (const auto* c = reg.try_get<ECS::CameraComponent>(e)) {
|
||||
out << YAML::Key << "Camera" << YAML::Value << YAML::Flow
|
||||
out << YAML::Key << "Attached" << YAML::Value
|
||||
<< YAML::BeginMap;
|
||||
out << YAML::Key << "Primary" << YAML::Value << c->primary;
|
||||
out << YAML::Key << "Active" << YAML::Value << c->active;
|
||||
@@ -583,185 +616,14 @@ namespace Engine::Serialization {
|
||||
out << YAML::EndMap; // Entity
|
||||
}
|
||||
|
||||
} // 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;
|
||||
|
||||
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);
|
||||
pe.parentUuid = e["Parent"].as<u64>(0);
|
||||
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;
|
||||
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));
|
||||
|
||||
const YAML::Node comps = pe.comps; // 拷贝初始化,缺省 Null
|
||||
|
||||
// 组件应用(场景加载 / 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()));
|
||||
@@ -812,6 +674,8 @@ namespace Engine::Serialization {
|
||||
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);
|
||||
@@ -1030,6 +894,200 @@ namespace Engine::Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
// 父子重链(场景加载 / 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 实体(视觉等价)。仅当旧键
|
||||
// 真实存在且实体列表没有环境组件时触发(新档缺省键不触发;
|
||||
@@ -1051,21 +1109,8 @@ namespace Engine::Serialization {
|
||||
legacyEnv);
|
||||
}
|
||||
|
||||
// ---- 阶段 3:重链父子(两遍解析:父可能出现在子之后)。
|
||||
// keepWorld=false:local TRS 已从文件就位(相对父语义),
|
||||
// 只建链标脏。防环由 SetParent 兜底(手编档成环 → 告警 + 挂根)
|
||||
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);
|
||||
}
|
||||
// ---- 阶段 3:重链父子(两遍解析:父可能出现在子之后)----
|
||||
RelinkParents(scene, parsed, byUuid);
|
||||
|
||||
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
||||
// 显式补算,否则首帧 World = 单位阵
|
||||
@@ -1123,4 +1168,157 @@ namespace Engine::Serialization {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user