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).
This commit is contained in:
2026-10-03 18:45:03 +08:00
parent cad0481bc0
commit b6d0868e15
7 changed files with 280 additions and 65 deletions
@@ -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>(
ECS::MeshRendererComponent{ref});
handle.GetComponent<ECS::MeshRendererComponent>().active =
n["Active"].as<bool>(true); // 缺省 true 向前兼容
}
else {
ENGINE_WARN("SceneSerializer: model unresolved "
@@ -536,11 +546,13 @@ 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.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"]) {
@@ -553,6 +565,7 @@ namespace Engine::Serialization {
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"]) {
@@ -604,6 +617,7 @@ namespace Engine::Serialization {
rb.friction = n["Friction"].as<f32>(rb.friction);
rb.restitution = n["Restitution"].as<f32>(rb.restitution);
rb.isSensor = n["IsSensor"].as<bool>(rb.isSensor);
rb.active = n["Active"].as<bool>(true);
// bodyId/bodyValid/velocity 保持默认(对账重建)
}
@@ -623,6 +637,7 @@ namespace Engine::Serialization {
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"]) {
@@ -634,6 +649,7 @@ namespace Engine::Serialization {
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 对账重建,不存
}
@@ -641,6 +657,7 @@ namespace Engine::Serialization {
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);
}
}