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
+31 -9
View File
@@ -356,6 +356,19 @@ namespace Engine {
for (auto [e, rb, tr]: view.each()) {
const u32 bits = static_cast<u32>(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<entt::entity>(owner);
const bool ownerGone = !reg.valid(ownerEnt) ||
!reg.all_of<ECS::JointComponent>(ownerEnt);
const ECS::JointComponent* jc =
reg.valid(ownerEnt) ? reg.try_get<ECS::JointComponent>(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<ECS::JointComponent>();
for (auto [e, jc]: view.each()) {
if (!jc.active) {
continue;
}
const u32 bits = static_cast<u32>(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 &reg = m_Scene.GetRegistry();
// 1. 孤儿清理:实体销毁 / 组件被移除(Ref 析构即清理,无 body 负担)
// 1. 孤儿清理:实体销毁 / 组件被移除或禁用(Ref 析构即清理,无 body 负担)
for (auto it = m_Characters.begin(); it != m_Characters.end();) {
const auto e = static_cast<entt::entity>(it->first);
if (!reg.valid(e) ||
!reg.all_of<ECS::CharacterControllerComponent,
ECS::TransformComponent>(e)) {
const auto* cc = reg.valid(e)
? reg.try_get<ECS::CharacterControllerComponent>(e) : nullptr;
if (cc == nullptr || !cc->active ||
!reg.all_of<ECS::TransformComponent>(e)) {
it = m_Characters.erase(it);
} else {
++it;
}
}
// 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送
// 2. 遍历:缺失 → 创建;参数变更 → 重建;编辑器拖动 → 传送;禁用 → 跳过
auto view = reg.view<ECS::CharacterControllerComponent,
ECS::TransformComponent>();
for (auto [e, cc, tr]: view.each()) {
if (!cc.active) {
continue;
}
const u32 bits = static_cast<u32>(entt::to_integral(e));
CharacterController::Params p;
p.radius = cc.radius;