From b6d0868e15bdfc01366c2d1355eff79cbfe20395 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Sat, 3 Oct 2026 18:45:03 +0800 Subject: [PATCH] feat(ecs): per-component enable/disable + inspector rework Add `active` flag to the 8 behavior components (Unity enabled / UE SetActive semantics; structural Id/Name/Transform excluded). Consumers skip disabled components: renderer (mesh/light/anim), primary camera election, physics reconciliation (body/constraint/character destroyed and rebuilt on toggle), scripts keep instance but stop ticking. Inspector: component headers now carry an enable checkbox and a remove button, Add-Component menu greys out attached entries, and conflict hints added (RigidBody <-> CharacterController mutual exclusion, Joint requires RigidBody, Animation requires MeshRenderer, unregistered script name). Scenes persist Active per component (default true). --- App/editor/InspectorPanel.cpp | 268 +++++++++++++++---- Engine/include/Engine/ECS/Components.h | 8 + Engine/src/ECS/Scene.cpp | 2 +- Engine/src/Physics/PhysicsWorld.cpp | 40 ++- Engine/src/Renderer/SceneRenderer.cpp | 3 + Engine/src/Scripting/ScriptRuntime.cpp | 7 +- Engine/src/Serialization/SceneSerializer.cpp | 17 ++ 7 files changed, 280 insertions(+), 65 deletions(-) diff --git a/App/editor/InspectorPanel.cpp b/App/editor/InspectorPanel.cpp index 66af60e..508ed64 100644 --- a/App/editor/InspectorPanel.cpp +++ b/App/editor/InspectorPanel.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace { @@ -64,6 +65,44 @@ namespace ImGui::PopID(); return changed; } + + // 组件区块头(Unity 语义):折叠标题 + 右缘"启用"勾选与"删除"按钮。 + // active 传 nullptr = 无启停(结构性组件);removable = 显示删除钮, + // 点击时置 *removeFlag,调用方在区块末尾统一移除(避免边画边删) + bool ComponentHeader(const char* label, bool* active, bool removable, + bool* removeFlag) + { + ImGui::PushID(label); + bool open; + if (active != nullptr) + { + open = ImGui::CollapsingHeader( + label, ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_AllowOverlap); + const float rowH = ImGui::GetItemRectSize().y; + float x = ImGui::GetWindowWidth() - 32.0f; + if (removable) + { + ImGui::SameLine(x); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 1.0f); + if (ImGui::Button("-", ImVec2(24.0f, rowH - 4.0f))) + { + *removeFlag = true; + } + x -= 30.0f; + } + ImGui::SameLine(x); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 1.0f); + ImGui::Checkbox("##active", active); + } + else + { + open = ImGui::CollapsingHeader(label, + ImGuiTreeNodeFlags_DefaultOpen); + } + ImGui::PopID(); + return open; + } } void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) @@ -85,61 +124,47 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) } ImGui::Separator(); - // ---- Add Component ---- + // ---- Add Component(已挂载项置灰:Unity Add Component 语义)---- if (ImGui::Button("Add Component")) { ImGui::OpenPopup("add_component"); } if (ImGui::BeginPopup("add_component")) { - if (!reg.all_of(selected) && - ImGui::MenuItem("Light")) + auto menuItem = [&](auto typeTag, const char* label) { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Camera")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Animation")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Name")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("RigidBody")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Joint")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Character Controller")) - { - reg.emplace(selected); - } - if (!reg.all_of(selected) && - ImGui::MenuItem("Script")) - { - reg.emplace(selected); - } + using T = std::remove_pointer_t; + const bool has = reg.all_of(selected); + if (has) + { + ImGui::BeginDisabled(true); + } + if (ImGui::MenuItem(label)) + { + reg.emplace(selected); + } + if (has) + { + ImGui::EndDisabled(); + } + }; + menuItem(static_cast(nullptr), "Light"); + menuItem(static_cast(nullptr), "Camera"); + menuItem(static_cast(nullptr), "Animation"); + menuItem(static_cast(nullptr), "Name"); + menuItem(static_cast(nullptr), "RigidBody"); + menuItem(static_cast(nullptr), "Joint"); + menuItem(static_cast(nullptr), + "Character Controller"); + menuItem(static_cast(nullptr), "Script"); ImGui::EndPopup(); } ImGui::Separator(); - // ---- Transform ---- + // ---- Transform(结构性组件:不可删、无启停)---- if (auto* t = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) + if (ComponentHeader("Transform", nullptr, false, nullptr)) { glm::vec3 pos = t->GetPosition(); if (DragVec3Row("position", pos, 0.05f)) @@ -162,9 +187,13 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) // ---- MeshRenderer ---- if (auto* mr = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("MeshRenderer", - ImGuiTreeNodeFlags_DefaultOpen)) + bool removeMr = false; + if (ComponentHeader("MeshRenderer", &mr->active, true, &removeMr)) { + if (!mr->active) + { + ImGui::BeginDisabled(true); // 禁用 = 内容置灰(Unity 语义) + } if (const Engine::Model* model = ctx.assets->GetModel(mr->model); model != nullptr) { @@ -179,14 +208,27 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "model: INVALID (dangling ref)"); } + if (!mr->active) + { + ImGui::EndDisabled(); + } + } + if (removeMr) + { + reg.remove(selected); } } // ---- Light ---- if (auto* light = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) + bool removeLight = false; + if (ComponentHeader("Light", &light->active, true, &removeLight)) { + if (!light->active) + { + ImGui::BeginDisabled(true); + } int type = static_cast(light->type); if (ImGui::Combo("type", &type, "Directional\0Point\0")) { @@ -204,14 +246,27 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::DragFloat("radius", &light->radius, 0.1f, 0.0f, 100.0f); ImGui::TextDisabled("(position = entity Transform)"); } + if (!light->active) + { + ImGui::EndDisabled(); + } + } + if (removeLight) + { + reg.remove(selected); } } // ---- Camera ---- if (auto* cam = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) + bool removeCam = false; + if (ComponentHeader("Camera", &cam->active, true, &removeCam)) { + if (!cam->active) + { + ImGui::BeginDisabled(true); + } if (ImGui::Checkbox("primary", &cam->primary) && cam->primary) { // primary 互斥:启用本相机时清掉其余实体的 primary @@ -237,6 +292,14 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ctx.editorCamera->AlignTo(*tr); } } + if (!cam->active) + { + ImGui::EndDisabled(); + } + } + if (removeCam) + { + reg.remove(selected); } } @@ -244,8 +307,21 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) // type/collider/mass 变更 → PhysicsWorld 重建 body;其余参数即时生效 if (auto* rb = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("RigidBody", ImGuiTreeNodeFlags_DefaultOpen)) + bool removeRb = false; + if (ComponentHeader("RigidBody", &rb->active, true, &removeRb)) { + if (!rb->active) + { + ImGui::BeginDisabled(true); + } + // 冲突提示:同实体挂 CharacterController 会同时建 body 与 + // CharacterVirtual,两个"运动主体"互相打架 + if (reg.all_of(selected)) + { + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), + "! conflicts with Character Controller " + "(disable or remove one)"); + } int type = static_cast(rb->type); if (ImGui::Combo("type", &type, "Static\0Dynamic\0Kinematic\0")) { @@ -347,14 +423,33 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) s_debugImpulse); } } + if (!rb->active) + { + ImGui::EndDisabled(); + } + } + if (removeRb) + { + reg.remove(selected); } } // ---- Joint(约束:两端 body 就绪后由 PhysicsWorld 对账创建)---- if (auto* jc = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Joint", ImGuiTreeNodeFlags_DefaultOpen)) + bool removeJc = false; + if (ComponentHeader("Joint", &jc->active, true, &removeJc)) { + if (!jc->active) + { + ImGui::BeginDisabled(true); + } + // 依赖提示:约束挂在两端刚体上,本体无 RigidBody 无法创建 + if (!reg.all_of(selected)) + { + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), + "! requires a RigidBody on this entity"); + } int type = static_cast(jc->type); if (ImGui::Combo("type", &type, "Fixed\0Hinge\0Ball\0")) { @@ -399,15 +494,34 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) ImGui::DragFloat("max deg", &jc->limitMax, 1.0f, 0.0f, 180.0f); } } + if (!jc->active) + { + ImGui::EndDisabled(); + } + } + if (removeJc) + { + reg.remove(selected); } } // ---- CharacterController(参数编辑;状态在 PhysicsWorld 运行时)---- if (auto* cc = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Character Controller", - ImGuiTreeNodeFlags_DefaultOpen)) + bool removeCc = false; + if (ComponentHeader("Character Controller", &cc->active, true, &removeCc)) { + if (!cc->active) + { + ImGui::BeginDisabled(true); + } + // 冲突提示:同实体挂 RigidBody 会同时建 CharacterVirtual 与 body + if (reg.all_of(selected)) + { + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), + "! conflicts with RigidBody " + "(disable or remove one)"); + } // 实体原点 = 脚底(胶囊底端),总高 = 2*(halfHeight + radius) ImGui::DragFloat("radius", &cc->radius, 0.02f, 0.05f, 5.0f); ImGui::DragFloat("halfHeight", &cc->capsuleHalfHeight, 0.02f, 0.0f, 5.0f); @@ -431,14 +545,27 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) phy.Jump(bits, 5.0f); } } + if (!cc->active) + { + ImGui::EndDisabled(); + } + } + if (removeCc) + { + reg.remove(selected); } } // ---- Script(按名解析注册表脚本;实例生命周期在 ScriptRuntime)---- if (auto* sc = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen)) + bool removeSc = false; + if (ComponentHeader("Script", &sc->active, true, &removeSc)) { + if (!sc->active) + { + ImGui::BeginDisabled(true); + } // 下拉列出全部已注册脚本(注册序);"" = 清空(对账自动销毁实例) const auto& names = Engine::Scripting::ScriptRegistry::Names(); std::vector items; @@ -464,20 +591,45 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) { ImGui::TextDisabled("no script assigned"); } + // 已填名但注册表查无此类:场景加载早于脚本注册/改名都会走到这 + else if (cur == 0) + { + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), + "! script not registered: %s", + sc->scriptName.c_str()); + } + if (!sc->active) + { + ImGui::EndDisabled(); + } + } + if (removeSc) + { + reg.remove(selected); } } // ---- Animation ---- if (auto* anim = reg.try_get(selected)) { - if (ImGui::CollapsingHeader("Animation", - ImGuiTreeNodeFlags_DefaultOpen)) + bool removeAnim = false; + if (ComponentHeader("Animation", &anim->active, true, &removeAnim)) { + if (!anim->active) + { + ImGui::BeginDisabled(true); + } const auto* mr = reg.try_get(selected); const Engine::Model* model = mr ? ctx.assets->GetModel(mr->model) : nullptr; - if (model != nullptr && model->HasAnimations()) + // 依赖提示:动画作用在 MeshRenderer 的模型骨骼上 + if (mr == nullptr) + { + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), + "! requires a MeshRenderer component"); + } + else if (model != nullptr && model->HasAnimations()) { const auto& clips = model->GetClips(); std::vector clipNames; @@ -508,6 +660,14 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected) { ImGui::TextDisabled("(no animation clips on model)"); } + if (!anim->active) + { + ImGui::EndDisabled(); + } + } + if (removeAnim) + { + reg.remove(selected); } } diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h index a648940..096d307 100644 --- a/Engine/include/Engine/ECS/Components.h +++ b/Engine/include/Engine/ECS/Components.h @@ -123,12 +123,14 @@ namespace Engine::ECS { // ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。 struct MeshRendererComponent { ModelRef model; + bool active = true; // 禁用 = 渲染/拾取跳过(Unity enabled / UE SetActive) }; // 骨骼动画播放状态(纯数据 + 便捷控制)。 // 采样/局部层混合逻辑在 SceneRenderer::BuildFrame(渲染侧唯一消费者); // ImGui 面板经 Play()/字段直接驱动。无此组件 = 按绑定姿势渲染。 struct AnimationComponent { + bool active = true; // 禁用 = 暂停采样(按当前帧姿态定格) int clipIndex = 0; // 当前 clip(Model::GetClips 下标) bool playing = true; bool loop = true; @@ -158,6 +160,7 @@ namespace Engine::ECS { // 相机。一个场景中 primary=true 的实体是当前渲染视角。 struct CameraComponent { + bool active = true; // 禁用 = 退出 primary 候选 Camera camera; bool primary = false; }; @@ -166,6 +169,7 @@ namespace Engine::ECS { // Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光) // Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减 struct LightComponent { + bool active = true; // 禁用 = 不进光照 enum class Type : u8 { Directional = 0, Point = 1 }; Type type = Type::Directional; glm::vec3 direction{0.0f, -1.0f, 0.0f}; // 仅方向光 @@ -183,6 +187,7 @@ namespace Engine::ECS { // 角色 = CharacterControllerComponent(CharacterVirtual),载具 = VehicleComponent // (VehicleConstraint)——都走 PhysicsWorld 门面,不复用本组件。 struct RigidBodyComponent { + bool active = true; // 禁用 = body 销毁/不建(对账自然消化) enum class BodyType : u8 { Static = 0, Dynamic = 1, Kinematic = 2 }; // Mesh:用实体 MeshRenderer 的视觉网格建碰撞(Unity MeshCollider // 语义)——Static → Jolt MeshShape(精确三角网格,不可移动); @@ -241,6 +246,7 @@ namespace Engine::ECS { // target 存 UUID(跨存档稳定),惰性解析——SyncScene 每帧按 UUID 反查 // 实体:序列化无需两遍解析,UUID 再生后自然失联,加载顺序无关 struct JointComponent { + bool active = true; // 禁用 = 约束销毁/不建(对账自然消化) enum class Type : u8 { Fixed = 0, Hinge = 1, Ball = 2 }; Type type = Type::Fixed; UUID target{}; // 目标实体(0 = 未设置,不建约束) @@ -264,6 +270,7 @@ namespace Engine::ECS { // 只存参数(序列化:存参数不存状态),速度/着地等运行时状态在 // PhysicsWorld 的 CharacterController 里(对账重建) struct CharacterControllerComponent { + bool active = true; // 禁用 = 角色移除(重启用按当前 Transform 重建) f32 radius = 0.3f; // 胶囊半径 f32 capsuleHalfHeight = 0.4f; // 圆柱段半高(总高 = 2*(hh+r)) f32 mass = 70.0f; @@ -278,6 +285,7 @@ namespace Engine::ECS { // ENGINE_REGISTER_SCRIPT 自注册;将来 Lua/C# 脚本同键解析。 // 实例生命周期由 ScriptRuntime 对账管理(不进组件,保持可拷贝) struct ScriptComponent { + bool active = true; // 禁用 = 实例保留但停 OnUpdate(Unity disable) std::string scriptName; bool operator==(const ScriptComponent&) const = default; diff --git a/Engine/src/ECS/Scene.cpp b/Engine/src/ECS/Scene.cpp index d1116be..86e16c5 100644 --- a/Engine/src/ECS/Scene.cpp +++ b/Engine/src/ECS/Scene.cpp @@ -81,7 +81,7 @@ namespace Engine::ECS { CameraComponent* found = nullptr; Entity foundEntity = entt::null; m_Registry.view().each([&](Entity e, CameraComponent& cc) { - if (!found && cc.primary) { + if (!found && cc.primary && cc.active) { // 禁用相机退出候选 found = &cc; foundEntity = e; } diff --git a/Engine/src/Physics/PhysicsWorld.cpp b/Engine/src/Physics/PhysicsWorld.cpp index 2365e64..9f287e4 100644 --- a/Engine/src/Physics/PhysicsWorld.cpp +++ b/Engine/src/Physics/PhysicsWorld.cpp @@ -356,6 +356,19 @@ namespace Engine { for (auto [e, rb, tr]: view.each()) { const u32 bits = static_cast(entt::to_integral(e)); const auto found = m_Bodies.find(bits); + + // 禁用 = 视为组件不存在:销毁 body(对账自然消化),重启用即重建 + if (!rb.active) { + if (found != m_Bodies.end()) { + bi.RemoveBody(found->second.id); + bi.DestroyBody(found->second.id); + m_Bodies.erase(found); + invalidated.push_back(bits); + } + rb.bodyValid = false; + continue; + } + if (found == m_Bodies.end() || !rb.bodyValid) { if (found != m_Bodies.end()) { bi.RemoveBody(found->second.id); @@ -437,12 +450,14 @@ namespace Engine { != invalidated.end(); }; - // 1. owner 实体/组件没了,或任一端 body 本帧被销毁/重建 → 移除 + // 1. owner 实体/组件没了(或被禁用),或任一端 body 本帧被销毁/重建 → 移除 for (auto it = m_Constraints.begin(); it != m_Constraints.end();) { const u32 owner = it->first; const auto ownerEnt = static_cast(owner); - const bool ownerGone = !reg.valid(ownerEnt) || - !reg.all_of(ownerEnt); + const ECS::JointComponent* jc = + reg.valid(ownerEnt) ? reg.try_get(ownerEnt) + : nullptr; + const bool ownerGone = jc == nullptr || !jc->active; if (ownerGone || tainted(owner) || tainted(it->second.targetBits)) { m_System->RemoveConstraint(it->second.constraint.GetPtr()); it = m_Constraints.erase(it); @@ -451,10 +466,13 @@ namespace Engine { } } - // 2. 遍历组件:快照变更 → 重建;缺失 → 创建。 + // 2. 遍历组件:快照变更 → 重建;缺失 → 创建;禁用 → 跳过(上面已清)。 // target 惰性按 UUID 反查(UUID 失联/body 未就绪 → 下帧再试) auto view = reg.view(); for (auto [e, jc]: view.each()) { + if (!jc.active) { + continue; + } const u32 bits = static_cast(entt::to_integral(e)); const auto found = m_Constraints.find(bits); if (found != m_Constraints.end()) { @@ -584,22 +602,26 @@ namespace Engine { void PhysicsWorld::SyncCharacters() { auto ® = m_Scene.GetRegistry(); - // 1. 孤儿清理:实体销毁 / 组件被移除(Ref 析构即清理,无 body 负担) + // 1. 孤儿清理:实体销毁 / 组件被移除或禁用(Ref 析构即清理,无 body 负担) for (auto it = m_Characters.begin(); it != m_Characters.end();) { const auto e = static_cast(it->first); - if (!reg.valid(e) || - !reg.all_of(e)) { + const auto* cc = reg.valid(e) + ? reg.try_get(e) : nullptr; + if (cc == nullptr || !cc->active || + !reg.all_of(e)) { it = m_Characters.erase(it); } else { ++it; } } - // 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送 + // 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送;禁用 → 跳过 auto view = reg.view(); for (auto [e, cc, tr]: view.each()) { + if (!cc.active) { + continue; + } const u32 bits = static_cast(entt::to_integral(e)); CharacterController::Params p; p.radius = cc.radius; diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 8d70775..281aa65 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -328,6 +328,7 @@ namespace Engine u32 collectedCount = 0; scene.Each( [&](Entity, TransformComponent& t, LightComponent& light) { + if (!light.active) return; // 组件禁用 = 不参与光照 if (collectedCount >= kMaxLights) return; auto& dst = collected[collectedCount++]; @@ -392,6 +393,7 @@ namespace Engine m_EntitySkinBase.clear(); scene.Each( [&](Entity entity, MeshRendererComponent& mr, AnimationComponent& anim) { + if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动) const Model* model = assets.GetModel(mr.model); if (!model || !model->HasAnimations()) return; const auto& clips = model->GetClips(); @@ -513,6 +515,7 @@ namespace Engine scene.Each( [&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) { + if (!mr.active) return; // 组件禁用 = 不渲染不拾取 Model* model = assets.GetModel(mr.model); if (!model) return; diff --git a/Engine/src/Scripting/ScriptRuntime.cpp b/Engine/src/Scripting/ScriptRuntime.cpp index a088489..2f12127 100644 --- a/Engine/src/Scripting/ScriptRuntime.cpp +++ b/Engine/src/Scripting/ScriptRuntime.cpp @@ -22,9 +22,14 @@ namespace Engine { auto& reg = m_Scene.GetRegistry(); for (auto& [bits, inst] : m_Instances) { const auto e = static_cast(bits); - if (!reg.valid(e) || !reg.any_of(e)) { + const ECS::ScriptComponent* sc = reg.valid(e) + ? reg.try_get(e) : nullptr; + if (!sc) { continue; // 延迟销毁队列里,下帧 Sync 清理 } + if (!sc->active) { + continue; // 禁用 = 保留实例停 tick(Unity disable 语义) + } if (!inst.created) { inst.behavior->OnCreate(); inst.created = true; diff --git a/Engine/src/Serialization/SceneSerializer.cpp b/Engine/src/Serialization/SceneSerializer.cpp index 3cdbf22..441d189 100644 --- a/Engine/src/Serialization/SceneSerializer.cpp +++ b/Engine/src/Serialization/SceneSerializer.cpp @@ -218,6 +218,7 @@ namespace Engine::Serialization { } 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 { @@ -233,6 +234,7 @@ namespace Engine::Serialization { 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; } @@ -240,6 +242,7 @@ namespace Engine::Serialization { 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; } @@ -254,6 +257,7 @@ namespace Engine::Serialization { 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; } @@ -304,6 +308,7 @@ namespace Engine::Serialization { << rb->restitution; out << YAML::Key << "IsSensor" << YAML::Value << rb->isSensor; // bodyId/bodyValid/velocity 由 PhysicsWorld 对账重建,不存 + out << YAML::Key << "Active" << YAML::Value << rb->active; out << YAML::EndMap; } @@ -325,6 +330,7 @@ namespace Engine::Serialization { << 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; } @@ -340,6 +346,7 @@ namespace Engine::Serialization { << cc->maxSlopeAngle; out << YAML::Key << "StepHeight" << YAML::Value << cc->stepHeight; + out << YAML::Key << "Active" << YAML::Value << cc->active; out << YAML::EndMap; } @@ -348,6 +355,7 @@ namespace Engine::Serialization { 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; } @@ -520,6 +528,8 @@ namespace Engine::Serialization { ref.Valid()) { handle.AddComponent( ECS::MeshRendererComponent{ref}); + handle.GetComponent().active = + n["Active"].as(true); // 缺省 true 向前兼容 } else { ENGINE_WARN("SceneSerializer: model unresolved " @@ -536,11 +546,13 @@ namespace Engine::Serialization { a.playing = n["Playing"].as(a.playing); a.loop = n["Loop"].as(a.loop); a.speed = n["Speed"].as(a.speed); + a.active = n["Active"].as(true); } if (const YAML::Node n = comps["Camera"]) { auto& c = handle.AddComponent(); c.primary = n["Primary"].as(false); + c.active = n["Active"].as(true); } if (const YAML::Node n = comps["Light"]) { @@ -553,6 +565,7 @@ namespace Engine::Serialization { l.color = ReadVec4(n["Color"], l.color); l.radius = n["Radius"].as(l.radius); l.castShadow = n["CastShadow"].as(l.castShadow); + l.active = n["Active"].as(true); } if (const YAML::Node n = comps["RigidBody"]) { @@ -604,6 +617,7 @@ namespace Engine::Serialization { rb.friction = n["Friction"].as(rb.friction); rb.restitution = n["Restitution"].as(rb.restitution); rb.isSensor = n["IsSensor"].as(rb.isSensor); + rb.active = n["Active"].as(true); // bodyId/bodyValid/velocity 保持默认(对账重建) } @@ -623,6 +637,7 @@ namespace Engine::Serialization { jc.enableLimit = n["EnableLimit"].as(jc.enableLimit); jc.limitMin = n["LimitMin"].as(jc.limitMin); jc.limitMax = n["LimitMax"].as(jc.limitMax); + jc.active = n["Active"].as(true); } if (const YAML::Node n = comps["CharacterController"]) { @@ -634,6 +649,7 @@ namespace Engine::Serialization { cc.mass = n["Mass"].as(cc.mass); cc.maxSlopeAngle = n["MaxSlopeAngle"].as(cc.maxSlopeAngle); cc.stepHeight = n["StepHeight"].as(cc.stepHeight); + cc.active = n["Active"].as(true); // 状态(速度/着地)由 PhysicsWorld 对账重建,不存 } @@ -641,6 +657,7 @@ namespace Engine::Serialization { if (const YAML::Node n = comps["Script"]) { auto& sc = handle.AddComponent(); sc.scriptName = n["ScriptName"].as(""); + sc.active = n["Active"].as(true); } }