From a5df60769f6b63890a17759e750271f9ac74a9f5 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sat, 25 Apr 2026 23:08:00 +0300 Subject: [PATCH] Now repeated smart object action works perfectly --- src/features/editScene/EditorApp.cpp | 15 ++--- .../editScene/components/BehaviorTree.hpp | 7 ++- .../editScene/components/Character.hpp | 17 +++++- .../editScene/systems/BehaviorTreeSystem.cpp | 25 ++++++++- .../editScene/systems/BehaviorTreeSystem.hpp | 5 +- .../editScene/systems/CharacterSystem.cpp | 55 ++++++++++++++++++- .../editScene/systems/CharacterSystem.hpp | 15 +++++ .../editScene/systems/SmartObjectSystem.cpp | 29 ++++++++-- .../editScene/ui/BehaviorTreeEditor.cpp | 16 +++++- .../editScene/ui/InlineBehaviorTreeEditor.cpp | 8 ++- 10 files changed, 172 insertions(+), 20 deletions(-) diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 06d76b7..6e1a0de 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -309,8 +309,15 @@ void EditorApp::setup() m_animationTreeSystem = std::make_unique( m_world, m_sceneMgr); m_animationTreeSystem->initialize(); + + // Setup Character physics system (needed by BehaviorTreeSystem) + m_characterSystem = + std::make_unique(m_world, m_sceneMgr); + m_characterSystem->initialize(); + m_behaviorTreeSystem = std::make_unique( - m_world, m_sceneMgr, m_animationTreeSystem.get()); + m_world, m_sceneMgr, m_animationTreeSystem.get(), + m_characterSystem.get()); // Setup NavMesh system m_navMeshSystem = @@ -324,12 +331,6 @@ void EditorApp::setup() m_smartObjectSystem->setAnimationTreeSystem( m_animationTreeSystem.get()); - // Setup Character physics system - - m_characterSystem = - std::make_unique(m_world, m_sceneMgr); - m_characterSystem->initialize(); - // Setup CellGrid system m_cellGridSystem = std::make_unique(m_world, m_sceneMgr); diff --git a/src/features/editScene/components/BehaviorTree.hpp b/src/features/editScene/components/BehaviorTree.hpp index 130bf71..4f2b1fd 100644 --- a/src/features/editScene/components/BehaviorTree.hpp +++ b/src/features/editScene/components/BehaviorTree.hpp @@ -28,6 +28,10 @@ * name = child entity name to teleport to. * The character is positioned at the child's absolute * world transform (position + orientation). + * "disablePhysics" - Leaf: removes character's JPH::BodyID from physics + * system so physics no longer interferes with animation. + * "enablePhysics" - Leaf: re-adds character's JPH::BodyID to physics + * system to restore physics simulation. */ struct BehaviorTreeNode { Ogre::String type = "task"; @@ -68,7 +72,8 @@ struct BehaviorTreeNode { type == "isAnimationEnded" || type == "setBit" || type == "checkBit" || type == "setValue" || type == "checkValue" || type == "blackboardDump" || - type == "delay" || type == "teleportToChild"; + type == "delay" || type == "teleportToChild" || + type == "disablePhysics" || type == "enablePhysics"; } }; diff --git a/src/features/editScene/components/Character.hpp b/src/features/editScene/components/Character.hpp index 2fdef13..32f85c5 100644 --- a/src/features/editScene/components/Character.hpp +++ b/src/features/editScene/components/Character.hpp @@ -27,6 +27,13 @@ struct CharacterComponent { /* Enable/disable physics character */ bool enabled = true; + /* Physics was explicitly disabled (e.g. by behavior tree node). + * When true, the character's JPH::BodyID is removed from the physics + * system but the JPH::Character object is kept alive so it can be + * re-added later. This is separate from 'enabled' which controls + * whether the character system processes this entity at all. */ + bool physicsDisabled = false; + /* Dirty flag — triggers rebuild of the Jolt character */ bool dirty = true; @@ -35,8 +42,14 @@ struct CharacterComponent { float floorCheckDistance = 2.0f; bool useGravity = true; - float getHalfHeight() const { return height * 0.5f; } - float getTotalHeight() const { return height + 2.0f * radius; } + float getHalfHeight() const + { + return height * 0.5f; + } + float getTotalHeight() const + { + return height + 2.0f * radius; + } }; #endif // EDITSCENE_CHARACTER_HPP diff --git a/src/features/editScene/systems/BehaviorTreeSystem.cpp b/src/features/editScene/systems/BehaviorTreeSystem.cpp index 8a1d245..b830529 100644 --- a/src/features/editScene/systems/BehaviorTreeSystem.cpp +++ b/src/features/editScene/systems/BehaviorTreeSystem.cpp @@ -1,5 +1,6 @@ #include "BehaviorTreeSystem.hpp" #include "AnimationTreeSystem.hpp" +#include "CharacterSystem.hpp" #include "SmartObjectSystem.hpp" #include "../components/BehaviorTree.hpp" #include "../components/ActionDatabase.hpp" @@ -8,6 +9,7 @@ #include "../components/Transform.hpp" #include "../components/EntityName.hpp" #include "../components/Relationship.hpp" +#include "../components/Character.hpp" #include #include #include @@ -159,10 +161,12 @@ static bool compareValues(const Comparison &cmp, int actualInt, BehaviorTreeSystem::BehaviorTreeSystem(flecs::world &world, Ogre::SceneManager *sceneMgr, - AnimationTreeSystem *animSystem) + AnimationTreeSystem *animSystem, + CharacterSystem *charSystem) : m_world(world) , m_sceneMgr(sceneMgr) , m_animSystem(animSystem) + , m_charSystem(charSystem) { } @@ -458,6 +462,25 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e, return Status::success; } + /* --- Character physics disable/enable nodes --- */ + if (node.type == "disablePhysics") { + if (isNewlyActive(state, &node) && m_charSystem) { + m_charSystem->disablePhysics(e); + std::cout << "[BT] disablePhysics: entity=" << e.id() + << std::endl; + } + return Status::success; + } + + if (node.type == "enablePhysics") { + if (isNewlyActive(state, &node) && m_charSystem) { + m_charSystem->enablePhysics(e); + std::cout << "[BT] enablePhysics: entity=" << e.id() + << std::endl; + } + return Status::success; + } + /* --- Teleport to Smart Object child node --- */ if (node.type == "teleportToChild") { if (isNewlyActive(state, &node)) { diff --git a/src/features/editScene/systems/BehaviorTreeSystem.hpp b/src/features/editScene/systems/BehaviorTreeSystem.hpp index 67a5527..8100793 100644 --- a/src/features/editScene/systems/BehaviorTreeSystem.hpp +++ b/src/features/editScene/systems/BehaviorTreeSystem.hpp @@ -12,6 +12,7 @@ #include "../components/ActionDebug.hpp" class AnimationTreeSystem; +class CharacterSystem; /** * Evaluates data-driven BehaviorTreeComponent each frame. @@ -23,7 +24,8 @@ class AnimationTreeSystem; class BehaviorTreeSystem { public: BehaviorTreeSystem(flecs::world &world, Ogre::SceneManager *sceneMgr, - AnimationTreeSystem *animSystem); + AnimationTreeSystem *animSystem, + CharacterSystem *charSystem = nullptr); ~BehaviorTreeSystem(); void update(float deltaTime); @@ -78,6 +80,7 @@ private: flecs::world &m_world; Ogre::SceneManager *m_sceneMgr; AnimationTreeSystem *m_animSystem; + CharacterSystem *m_charSystem; std::unordered_map m_runnerStates; std::unordered_map m_actionDebugStates; diff --git a/src/features/editScene/systems/CharacterSystem.cpp b/src/features/editScene/systems/CharacterSystem.cpp index 74baf7f..461006b 100644 --- a/src/features/editScene/systems/CharacterSystem.cpp +++ b/src/features/editScene/systems/CharacterSystem.cpp @@ -191,6 +191,54 @@ void CharacterSystem::teardownEntity(flecs::entity e) m_states.erase(it); } +void CharacterSystem::disablePhysics(flecs::entity e) +{ + if (!m_physics) + return; + + auto it = m_states.find(e.id()); + if (it == m_states.end()) + return; + + CharacterState &state = it->second; + if (!state.character) + return; + + JPH::BodyID bodyID = state.character->GetBodyID(); + if (m_physics->isAdded(bodyID)) { + state.character->RemoveFromPhysicsSystem(); + } + + if (e.has()) { + auto &cc = e.get_mut(); + cc.physicsDisabled = true; + } +} + +void CharacterSystem::enablePhysics(flecs::entity e) +{ + if (!m_physics) + return; + + auto it = m_states.find(e.id()); + if (it == m_states.end()) + return; + + CharacterState &state = it->second; + if (!state.character) + return; + + JPH::BodyID bodyID = state.character->GetBodyID(); + if (!m_physics->isAdded(bodyID)) { + state.character->AddToPhysicsSystem(); + } + + if (e.has()) { + auto &cc = e.get_mut(); + cc.physicsDisabled = false; + } +} + void CharacterSystem::update(float deltaTime) { if (!m_initialized || !m_physics) @@ -217,6 +265,10 @@ void CharacterSystem::update(float deltaTime) if (!state.character || !state.sceneNode) return; + /* Skip physics processing when physics is disabled */ + if (cc.physicsDisabled) + return; + /* Read current physics position */ Ogre::Vector3 charPos = JoltPhysics::convert( state.character->GetPosition()); @@ -235,7 +287,8 @@ void CharacterSystem::update(float deltaTime) * Preserve physics-driven Y velocity when no explicit * vertical input is given so gravity/buoyancy/jumps * are not overwritten every frame. */ - JPH::Vec3 currentVel = state.character->GetLinearVelocity(); + JPH::Vec3 currentVel = + state.character->GetLinearVelocity(); JPH::Vec3 desiredVel = JoltPhysics::convert( cc.linearVelocity); JPH::Vec3 finalVel; diff --git a/src/features/editScene/systems/CharacterSystem.hpp b/src/features/editScene/systems/CharacterSystem.hpp index 38796f6..35764da 100644 --- a/src/features/editScene/systems/CharacterSystem.hpp +++ b/src/features/editScene/systems/CharacterSystem.hpp @@ -28,6 +28,21 @@ public: void initialize(); void update(float deltaTime); + /** + * Disable physics for a character entity. + * Removes the JPH::Character's BodyID from the physics system + * but keeps the JPH::Character object alive so it can be re-added later. + * Sets CharacterComponent::physicsDisabled = true. + */ + void disablePhysics(flecs::entity e); + + /** + * Enable physics for a character entity. + * Re-adds the JPH::Character's BodyID to the physics system. + * Sets CharacterComponent::physicsDisabled = false. + */ + void enablePhysics(flecs::entity e); + private: struct CharacterState { JPH::Character *character = nullptr; diff --git a/src/features/editScene/systems/SmartObjectSystem.cpp b/src/features/editScene/systems/SmartObjectSystem.cpp index 8ae46a0..ecdf2b1 100644 --- a/src/features/editScene/systems/SmartObjectSystem.cpp +++ b/src/features/editScene/systems/SmartObjectSystem.cpp @@ -915,21 +915,40 @@ void SmartObjectSystem::update(float deltaTime) // Kick off the action's // behavior tree via // BehaviorTreeSystem's - // ActionDebug path + // ActionDebug path. + // Only reset runTimer on the + // first frame of execution + // (when isRunning is false) so + // BehaviorTreeSystem re-initializes + // the runner state for a fresh + // execution. Do NOT reset every + // frame or isNewlyActive will + // fire repeatedly. + if (!debug.isRunning) { + debug.runTimer = 0.0f; + } debug.isRunning = true; debug.currentActionName = state.target.actionName; // Check if the behavior tree // has finished (sequence - // completed or failed) + // completed or failed). + // Only check completion when + // runTimer > 0 (at least one + // frame has passed since + // starting) to avoid + // immediately detecting + // completion on the same frame + // the tree was started. auto &btState = m_btSystem ->getActionDebugState( e.id()); - if (btState.treeResult != - BehaviorTreeSystem::Status:: - running) { + if (debug.runTimer > 0.0f && + btState.treeResult != + BehaviorTreeSystem:: + Status::running) { // Behavior tree // completed - stop // evaluation diff --git a/src/features/editScene/ui/BehaviorTreeEditor.cpp b/src/features/editScene/ui/BehaviorTreeEditor.cpp index 7061615..fa5b0f9 100644 --- a/src/features/editScene/ui/BehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/BehaviorTreeEditor.cpp @@ -23,6 +23,10 @@ static ImU32 nodeTypeColorU32(const Ogre::String &type) return IM_COL32(0xFF, 0xD7, 0x00, 0xFF); if (type == "teleportToChild") return IM_COL32(0xFF, 0x69, 0xB4, 0xFF); + if (type == "disablePhysics") + return IM_COL32(0x80, 0x80, 0x80, 0xFF); + if (type == "enablePhysics") + return IM_COL32(0x4C, 0xCC, 0x4C, 0xFF); return IM_COL32(0xFF, 0xFF, 0xFF, 0xFF); } @@ -196,6 +200,10 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "delay"); if (ImGui::MenuItem("Add Teleport To Child")) queueAddChild(&node, "teleportToChild"); + if (ImGui::MenuItem("Add Disable Physics")) + queueAddChild(&node, "disablePhysics"); + if (ImGui::MenuItem("Add Enable Physics")) + queueAddChild(&node, "enablePhysics"); } if (parent) { size_t idx = findChildIndex(*parent, &node); @@ -245,6 +253,10 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "delay"); if (ImGui::MenuItem("Teleport To Child")) queueAddChild(&node, "teleportToChild"); + if (ImGui::MenuItem("Disable Physics")) + queueAddChild(&node, "disablePhysics"); + if (ImGui::MenuItem("Enable Physics")) + queueAddChild(&node, "enablePhysics"); ImGui::EndPopup(); } } @@ -296,7 +308,9 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) "checkValue", "blackboardDump", "delay", - "teleportToChild" }; + "teleportToChild", + "disablePhysics", + "enablePhysics" }; int typeIdx = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) { diff --git a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp index bb4e500..46e2c96 100644 --- a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp @@ -92,6 +92,10 @@ static ImVec4 typeColorVec(const char *type) return ImVec4(1.0f, 0.84f, 0.0f, 1.0f); if (!strcmp(type, "teleportToChild")) return ImVec4(1.0f, 0.41f, 0.71f, 1.0f); + if (!strcmp(type, "disablePhysics")) + return ImVec4(0.5f, 0.5f, 0.5f, 1.0f); + if (!strcmp(type, "enablePhysics")) + return ImVec4(0.3f, 0.8f, 0.3f, 1.0f); return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); } @@ -254,7 +258,9 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) "checkValue", "blackboardDump", "delay", - "teleportToChild" }; + "teleportToChild", + "disablePhysics", + "enablePhysics" }; int current = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) {