新增 Prefab 系统:实体子树存取 + 编辑器 Save As Prefab/拖入实例化 + 脚本 Instantiate

This commit is contained in:
2026-10-08 22:01:26 +08:00
parent b89f431896
commit ed7cb417bd
8 changed files with 643 additions and 208 deletions
+10
View File
@@ -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,
+399 -201
View File
@@ -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