feat: parent-child transform hierarchy (UE Actor attach semantics)
- TransformComponent TRS = local (relative to parent); cached world SRT composed hierarchically (parent pos/rot/scale, no matrix decomposition shear under non-uniform parent scale) - HierarchyComponent (parent + children, lazily attached); Scene:: SetParent (keepWorld, cycle-safe) / GetParent / IsDescendantOf / SetWorldPose (world->local inverse, used by physics writeback & gizmo) - UpdateTransforms: DFS from roots, recompose only dirty-or-dirty- ancestor subtrees (parents before children) - DestroyEntity: descendants buried with parent (UE cascade) - Serialization v2: optional Parent uuid key, two-pass relink on load - Hierarchy panel: tree view, drag-drop reparent / drop-to-root detach, Create Child / Detach context menu; Inspector shows parent name - Consumers moved to world-space reads: lights, camera pos, physics bodies/characters/joints/collider baking, gizmo, F-focus, AlignTo
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -176,6 +177,7 @@ namespace Engine::Serialization {
|
||||
{
|
||||
u64 uuid = 0;
|
||||
std::string name = "entity";
|
||||
u64 parentUuid = 0; // 0 = 根实体(层级键缺省)
|
||||
YAML::Node comps; // 缺省 = Null(无组件)
|
||||
};
|
||||
|
||||
@@ -190,6 +192,13 @@ namespace Engine::Serialization {
|
||||
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;
|
||||
|
||||
@@ -524,6 +533,7 @@ namespace Engine::Serialization {
|
||||
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= 会抛)
|
||||
@@ -543,6 +553,7 @@ namespace Engine::Serialization {
|
||||
scene.Clear();
|
||||
|
||||
std::unordered_set<u64> seenUuids;
|
||||
std::unordered_map<u64, entt::entity> byUuid; // 第二遍重链用
|
||||
for (ParsedEntity& pe : parsed) {
|
||||
auto handle = scene.CreateEntity(pe.name);
|
||||
|
||||
@@ -552,6 +563,8 @@ namespace Engine::Serialization {
|
||||
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
|
||||
|
||||
@@ -734,6 +747,22 @@ namespace Engine::Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 阶段 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);
|
||||
}
|
||||
|
||||
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
||||
// 显式补算,否则首帧 World = 单位阵
|
||||
scene.UpdateTransforms();
|
||||
|
||||
Reference in New Issue
Block a user