Now repeated smart object action works perfectly

This commit is contained in:
2026-04-25 23:08:00 +03:00
parent 75ba39895f
commit a5df60769f
10 changed files with 172 additions and 20 deletions

View File

@@ -309,8 +309,15 @@ void EditorApp::setup()
m_animationTreeSystem = std::make_unique<AnimationTreeSystem>(
m_world, m_sceneMgr);
m_animationTreeSystem->initialize();
// Setup Character physics system (needed by BehaviorTreeSystem)
m_characterSystem =
std::make_unique<CharacterSystem>(m_world, m_sceneMgr);
m_characterSystem->initialize();
m_behaviorTreeSystem = std::make_unique<BehaviorTreeSystem>(
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<CharacterSystem>(m_world, m_sceneMgr);
m_characterSystem->initialize();
// Setup CellGrid system
m_cellGridSystem =
std::make_unique<CellGridSystem>(m_world, m_sceneMgr);

View File

@@ -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";
}
};

View File

@@ -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

View File

@@ -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 <OgreLogManager.h>
#include <iostream>
#include <cstdlib>
@@ -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)) {

View File

@@ -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<flecs::entity_t, RunnerState> m_runnerStates;
std::unordered_map<flecs::entity_t, RunnerState> m_actionDebugStates;

View File

@@ -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<CharacterComponent>()) {
auto &cc = e.get_mut<CharacterComponent>();
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<CharacterComponent>()) {
auto &cc = e.get_mut<CharacterComponent>();
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<JPH::Vec3>(
cc.linearVelocity);
JPH::Vec3 finalVel;

View File

@@ -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;

View File

@@ -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

View File

@@ -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]) {

View File

@@ -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]) {