Motion fixed
This commit is contained in:
@@ -20,6 +20,20 @@
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
|
||||
/**
|
||||
* Check if a character entity has root motion enabled in its
|
||||
* AnimationTreeComponent. When root motion is active, the
|
||||
* AnimationTreeSystem computes linearVelocity from animation
|
||||
* root bone displacement, so SmartObjectSystem should NOT
|
||||
* overwrite it with direct movement velocity.
|
||||
*/
|
||||
static bool hasRootMotion(flecs::entity e)
|
||||
{
|
||||
if (!e.has<AnimationTreeComponent>())
|
||||
return false;
|
||||
return e.get<AnimationTreeComponent>().useRootMotion;
|
||||
}
|
||||
|
||||
SmartObjectSystem *SmartObjectSystem::s_instance = nullptr;
|
||||
|
||||
SmartObjectSystem::SmartObjectSystem(flecs::world &world,
|
||||
@@ -119,8 +133,7 @@ void SmartObjectSystem::setLocomotionState(flecs::entity e,
|
||||
// Look up the path following state in PathFollowingComponent
|
||||
if (e.has<PathFollowingComponent>()) {
|
||||
auto &pf = e.get<PathFollowingComponent>();
|
||||
const PathFollowingState *pfState =
|
||||
pf.findState(animName);
|
||||
const PathFollowingState *pfState = pf.findState(animName);
|
||||
if (pfState) {
|
||||
// Apply ALL state machine name -> state name pairs
|
||||
for (const auto &pair : pfState->stateMachineStates) {
|
||||
@@ -562,8 +575,8 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
|
||||
// Use run speed if far away
|
||||
if (e.has<PathFollowingComponent>()) {
|
||||
auto &pf =
|
||||
e.get<PathFollowingComponent>();
|
||||
auto &pf = e.get<
|
||||
PathFollowingComponent>();
|
||||
if (distToTarget >
|
||||
pf.walkSpeed * 3.0f) {
|
||||
speed = pf.runSpeed;
|
||||
@@ -578,12 +591,19 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
setLocomotionState(e, "walk");
|
||||
}
|
||||
|
||||
cc.linearVelocity = toTarget * speed;
|
||||
// When root motion is enabled, the
|
||||
// AnimationTreeSystem already computes
|
||||
// linearVelocity from animation root
|
||||
// bone displacement. Do NOT overwrite
|
||||
// it with direct movement velocity.
|
||||
if (!hasRootMotion(e))
|
||||
cc.linearVelocity =
|
||||
toTarget * speed;
|
||||
|
||||
// Rotate character to face movement
|
||||
// direction (Y plane only)
|
||||
rotateTowards(e, toTarget, deltaTime);
|
||||
} else {
|
||||
} else if (!hasRootMotion(e)) {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
}
|
||||
return;
|
||||
@@ -642,167 +662,213 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
});
|
||||
|
||||
// Also process characters with ActionDebug (for editor testing)
|
||||
m_world.query<CharacterComponent, TransformComponent, ActionDebug>()
|
||||
.each([&](flecs::entity e, CharacterComponent &cc,
|
||||
TransformComponent &trans, ActionDebug &debug) {
|
||||
(void)trans;
|
||||
(void)debug;
|
||||
m_world.query<CharacterComponent, TransformComponent, ActionDebug>().each([&](flecs::entity
|
||||
e,
|
||||
CharacterComponent
|
||||
&cc,
|
||||
TransformComponent
|
||||
&trans,
|
||||
ActionDebug
|
||||
&debug) {
|
||||
(void)trans;
|
||||
(void)debug;
|
||||
|
||||
// Skip player-controlled characters - they are managed
|
||||
// by PlayerControllerSystem, not by SmartObjectSystem AI.
|
||||
if (playerCharacterIds.find(e.id()) !=
|
||||
playerCharacterIds.end())
|
||||
return;
|
||||
// Skip player-controlled characters - they are managed
|
||||
// by PlayerControllerSystem, not by SmartObjectSystem AI.
|
||||
if (playerCharacterIds.find(e.id()) != playerCharacterIds.end())
|
||||
return;
|
||||
|
||||
auto &state = m_states[e.id()];
|
||||
auto &state = m_states[e.id()];
|
||||
|
||||
// When the behavior tree is running (via ActionDebug),
|
||||
// do NOT override animation states - the behavior tree's
|
||||
// setAnimationState nodes should be the sole controller.
|
||||
if (debug.isRunning &&
|
||||
!debug.currentActionName.empty()) {
|
||||
// Behavior tree controls animations, skip
|
||||
// locomotion state override
|
||||
} else if (state.state == State::Idle) {
|
||||
setLocomotionState(e, "idle");
|
||||
// When the behavior tree is running (via ActionDebug),
|
||||
// do NOT override animation states - the behavior tree's
|
||||
// setAnimationState nodes should be the sole controller.
|
||||
if (debug.isRunning && !debug.currentActionName.empty()) {
|
||||
// Behavior tree controls animations, skip
|
||||
// locomotion state override
|
||||
} else if (state.state == State::Idle) {
|
||||
setLocomotionState(e, "idle");
|
||||
return;
|
||||
}
|
||||
|
||||
GoapBlackboard &bb = debug.blackboard;
|
||||
|
||||
// --- State: Pathfinding ---
|
||||
if (state.state == State::Pathfinding) {
|
||||
if (!state.target.smartObject.is_alive()) {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: target dead, going idle"
|
||||
<< std::endl;
|
||||
state.state = State::Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
GoapBlackboard &bb = debug.blackboard;
|
||||
Ogre::Vector3 charPos = getEntityPosition(e);
|
||||
Ogre::Vector3 objPos =
|
||||
getEntityPosition(state.target.smartObject);
|
||||
|
||||
// --- State: Pathfinding ---
|
||||
if (state.state == State::Pathfinding) {
|
||||
if (!state.target.smartObject.is_alive()) {
|
||||
auto &soComp = state.target.smartObject
|
||||
.get<SmartObjectComponent>();
|
||||
if (isInRange(charPos, objPos, soComp.radius,
|
||||
soComp.height)) {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: already in range, executing"
|
||||
<< std::endl;
|
||||
state.state = State::Executing;
|
||||
state.target.isExecuting = true;
|
||||
state.target.executionTimer = 0.0f;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
if (navmeshEntity.is_alive() && m_navSystem) {
|
||||
state.target.path.clear();
|
||||
state.target.pathIndex = 0;
|
||||
bool found = m_navSystem->findPath(
|
||||
navmeshEntity, charPos, objPos,
|
||||
state.target.path);
|
||||
if (found && !state.target.path.empty()) {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: target dead, going idle"
|
||||
<< std::endl;
|
||||
state.state = State::Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
Ogre::Vector3 charPos = getEntityPosition(e);
|
||||
Ogre::Vector3 objPos = getEntityPosition(
|
||||
state.target.smartObject);
|
||||
|
||||
auto &soComp =
|
||||
state.target.smartObject
|
||||
.get<SmartObjectComponent>();
|
||||
if (isInRange(charPos, objPos, soComp.radius,
|
||||
soComp.height)) {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: already in range, executing"
|
||||
<< std::endl;
|
||||
state.state = State::Executing;
|
||||
state.target.isExecuting = true;
|
||||
state.target.executionTimer = 0.0f;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
if (navmeshEntity.is_alive() && m_navSystem) {
|
||||
state.target.path.clear();
|
||||
state.target.pathIndex = 0;
|
||||
bool found = m_navSystem->findPath(
|
||||
navmeshEntity, charPos, objPos,
|
||||
state.target.path);
|
||||
if (found &&
|
||||
!state.target.path.empty()) {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: path found with "
|
||||
<< state.target.path
|
||||
.size()
|
||||
<< " waypoints"
|
||||
<< std::endl;
|
||||
state.state = State::Moving;
|
||||
} else {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: no path found, going idle"
|
||||
<< std::endl;
|
||||
state.state = State::Idle;
|
||||
cc.linearVelocity =
|
||||
Ogre::Vector3::ZERO;
|
||||
}
|
||||
<< "[SO] Pathfinding: path found with "
|
||||
<< state.target.path.size()
|
||||
<< " waypoints" << std::endl;
|
||||
state.state = State::Moving;
|
||||
} else {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: no navmesh, going direct"
|
||||
<< std::endl;
|
||||
state.target.path.clear();
|
||||
state.target.path.push_back(objPos);
|
||||
state.target.pathIndex = 0;
|
||||
state.state = State::Moving;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- State: Moving ---
|
||||
if (state.state == State::Moving) {
|
||||
if (!state.target.smartObject.is_alive() ||
|
||||
state.target.path.empty()) {
|
||||
std::cout
|
||||
<< "[SO] Moving: target dead or path empty, idle"
|
||||
<< "[SO] Pathfinding: no path found, going idle"
|
||||
<< std::endl;
|
||||
state.state = State::Idle;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
std::cout
|
||||
<< "[SO] Pathfinding: no navmesh, going direct"
|
||||
<< std::endl;
|
||||
state.target.path.clear();
|
||||
state.target.path.push_back(objPos);
|
||||
state.target.pathIndex = 0;
|
||||
state.state = State::Moving;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Ogre::Vector3 charPos = getEntityPosition(e);
|
||||
Ogre::Vector3 objPos = getEntityPosition(
|
||||
state.target.smartObject);
|
||||
// --- State: Moving ---
|
||||
if (state.state == State::Moving) {
|
||||
if (!state.target.smartObject.is_alive() ||
|
||||
state.target.path.empty()) {
|
||||
std::cout
|
||||
<< "[SO] Moving: target dead or path empty, idle"
|
||||
<< std::endl;
|
||||
state.state = State::Idle;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
auto &soComp =
|
||||
state.target.smartObject
|
||||
.get<SmartObjectComponent>();
|
||||
Ogre::Vector3 charPos = getEntityPosition(e);
|
||||
Ogre::Vector3 objPos =
|
||||
getEntityPosition(state.target.smartObject);
|
||||
|
||||
// Always check range first
|
||||
if (isInRange(charPos, objPos, soComp.radius,
|
||||
soComp.height)) {
|
||||
auto &soComp = state.target.smartObject
|
||||
.get<SmartObjectComponent>();
|
||||
|
||||
// Always check range first
|
||||
if (isInRange(charPos, objPos, soComp.radius,
|
||||
soComp.height)) {
|
||||
std::cout << "[SO] Moving: in range, executing"
|
||||
<< std::endl;
|
||||
state.state = State::Executing;
|
||||
state.target.isExecuting = true;
|
||||
state.target.executionTimer = 0.0f;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
state.target.pathRecalcTimer += deltaTime;
|
||||
if (state.target.pathRecalcTimer > 2.0f) {
|
||||
state.target.pathRecalcTimer = 0.0f;
|
||||
if (navmeshEntity.is_alive() && m_navSystem) {
|
||||
std::vector<Ogre::Vector3> newPath;
|
||||
bool found = m_navSystem->findPath(
|
||||
navmeshEntity, charPos, objPos,
|
||||
newPath);
|
||||
if (found && !newPath.empty()) {
|
||||
state.target.path = newPath;
|
||||
state.target.pathIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path is exhausted
|
||||
if (state.target.pathIndex >=
|
||||
(int)state.target.path.size()) {
|
||||
float distToObj = charPos.distance(objPos);
|
||||
std::cout
|
||||
<< "[SO] Moving: path exhausted, dist="
|
||||
<< distToObj
|
||||
<< " radius*2=" << soComp.radius * 2.0f
|
||||
<< std::endl;
|
||||
// If close enough, teleport to range
|
||||
// and execute
|
||||
if (distToObj < soComp.radius * 2.0f) {
|
||||
std::cout
|
||||
<< "[SO] Moving: in range, executing"
|
||||
<< "[SO] Moving: close enough, teleporting"
|
||||
<< std::endl;
|
||||
Ogre::Vector3 dirToObj =
|
||||
objPos - charPos;
|
||||
dirToObj.y = 0;
|
||||
float xzDist = dirToObj.length();
|
||||
if (xzDist > 0.01f) {
|
||||
dirToObj /= xzDist;
|
||||
Ogre::Vector3 newPos =
|
||||
objPos -
|
||||
dirToObj *
|
||||
(soComp.radius *
|
||||
0.5f);
|
||||
newPos.y = charPos.y;
|
||||
if (trans.node) {
|
||||
trans.node->setPosition(
|
||||
newPos);
|
||||
}
|
||||
trans.position = newPos;
|
||||
}
|
||||
state.state = State::Executing;
|
||||
state.target.isExecuting = true;
|
||||
state.target.executionTimer = 0.0f;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
// Too far - recalculate path
|
||||
std::cout << "[SO] Moving: too far, recalc path"
|
||||
<< std::endl;
|
||||
state.target.path.clear();
|
||||
state.target.path.push_back(objPos);
|
||||
state.target.pathIndex = 0;
|
||||
}
|
||||
|
||||
state.target.pathRecalcTimer += deltaTime;
|
||||
if (state.target.pathRecalcTimer > 2.0f) {
|
||||
state.target.pathRecalcTimer = 0.0f;
|
||||
if (navmeshEntity.is_alive() &&
|
||||
m_navSystem) {
|
||||
std::vector<Ogre::Vector3>
|
||||
newPath;
|
||||
bool found =
|
||||
m_navSystem->findPath(
|
||||
navmeshEntity,
|
||||
charPos, objPos,
|
||||
newPath);
|
||||
if (found && !newPath.empty()) {
|
||||
state.target.path =
|
||||
newPath;
|
||||
state.target.pathIndex =
|
||||
0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ogre::Vector3 targetPos =
|
||||
state.target.path[state.target.pathIndex];
|
||||
Ogre::Vector3 toTarget = targetPos - charPos;
|
||||
|
||||
// Check if path is exhausted
|
||||
// Waypoint arrival threshold - generous since
|
||||
// waypoints are just guide points
|
||||
const float WAYPOINT_THRESHOLD = 1.0f;
|
||||
if (toTarget.length() < WAYPOINT_THRESHOLD) {
|
||||
state.target.pathIndex++;
|
||||
if (state.target.pathIndex >=
|
||||
(int)state.target.path.size()) {
|
||||
// Path exhausted - teleport to
|
||||
// range and execute if close
|
||||
// enough
|
||||
float distToObj =
|
||||
charPos.distance(objPos);
|
||||
std::cout
|
||||
<< "[SO] Moving: path exhausted, dist="
|
||||
<< "[SO] Moving: last waypoint reached, dist="
|
||||
<< distToObj << " radius*2="
|
||||
<< soComp.radius * 2.0f
|
||||
<< std::endl;
|
||||
// If close enough, teleport to range
|
||||
// and execute
|
||||
if (distToObj < soComp.radius * 2.0f) {
|
||||
std::cout
|
||||
<< "[SO] Moving: close enough, teleporting"
|
||||
<< "[SO] Moving: close enough at waypoint, teleporting"
|
||||
<< std::endl;
|
||||
Ogre::Vector3 dirToObj =
|
||||
objPos - charPos;
|
||||
@@ -833,212 +899,134 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
}
|
||||
// Too far - recalculate path
|
||||
std::cout
|
||||
<< "[SO] Moving: too far, recalc path"
|
||||
<< "[SO] Moving: too far at waypoint, recalc"
|
||||
<< std::endl;
|
||||
state.target.path.clear();
|
||||
state.target.path.push_back(objPos);
|
||||
state.target.pathIndex = 0;
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
|
||||
Ogre::Vector3 targetPos =
|
||||
targetPos =
|
||||
state.target
|
||||
.path[state.target.pathIndex];
|
||||
Ogre::Vector3 toTarget = targetPos - charPos;
|
||||
|
||||
// Waypoint arrival threshold - generous since
|
||||
// waypoints are just guide points
|
||||
const float WAYPOINT_THRESHOLD = 1.0f;
|
||||
if (toTarget.length() < WAYPOINT_THRESHOLD) {
|
||||
state.target.pathIndex++;
|
||||
if (state.target.pathIndex >=
|
||||
(int)state.target.path.size()) {
|
||||
// Path exhausted - teleport to
|
||||
// range and execute if close
|
||||
// enough
|
||||
float distToObj =
|
||||
charPos.distance(
|
||||
objPos);
|
||||
std::cout
|
||||
<< "[SO] Moving: last waypoint reached, dist="
|
||||
<< distToObj
|
||||
<< " radius*2="
|
||||
<< soComp.radius * 2.0f
|
||||
<< std::endl;
|
||||
if (distToObj <
|
||||
soComp.radius * 2.0f) {
|
||||
std::cout
|
||||
<< "[SO] Moving: close enough at waypoint, teleporting"
|
||||
<< std::endl;
|
||||
Ogre::Vector3 dirToObj =
|
||||
objPos -
|
||||
charPos;
|
||||
dirToObj.y = 0;
|
||||
float xzDist =
|
||||
dirToObj.length();
|
||||
if (xzDist > 0.01f) {
|
||||
dirToObj /=
|
||||
xzDist;
|
||||
Ogre::Vector3 newPos =
|
||||
objPos -
|
||||
dirToObj *
|
||||
(soComp.radius *
|
||||
0.5f);
|
||||
newPos.y =
|
||||
charPos.y;
|
||||
if (trans.node) {
|
||||
trans.node
|
||||
->setPosition(
|
||||
newPos);
|
||||
}
|
||||
trans.position =
|
||||
newPos;
|
||||
}
|
||||
state.state =
|
||||
State::Executing;
|
||||
state.target
|
||||
.isExecuting =
|
||||
true;
|
||||
state.target
|
||||
.executionTimer =
|
||||
0.0f;
|
||||
cc.linearVelocity =
|
||||
Ogre::Vector3::
|
||||
ZERO;
|
||||
return;
|
||||
}
|
||||
// Too far - recalculate path
|
||||
std::cout
|
||||
<< "[SO] Moving: too far at waypoint, recalc"
|
||||
<< std::endl;
|
||||
state.target.path.clear();
|
||||
state.target.path.push_back(
|
||||
objPos);
|
||||
state.target.pathIndex = 0;
|
||||
cc.linearVelocity =
|
||||
Ogre::Vector3::ZERO;
|
||||
return;
|
||||
}
|
||||
targetPos =
|
||||
state.target.path
|
||||
[state.target.pathIndex];
|
||||
toTarget = targetPos - charPos;
|
||||
}
|
||||
|
||||
toTarget.y = 0;
|
||||
if (toTarget.squaredLength() > 0.0001f) {
|
||||
toTarget.normalise();
|
||||
|
||||
float distToTarget =
|
||||
(charPos - objPos).length();
|
||||
float speed = 2.5f;
|
||||
if (e.has<PathFollowingComponent>()) {
|
||||
auto &pf = e.get<PathFollowingComponent>();
|
||||
speed = pf.walkSpeed;
|
||||
if (distToTarget >
|
||||
pf.walkSpeed * 3.0f) {
|
||||
speed = pf.runSpeed;
|
||||
setLocomotionState(e, "run");
|
||||
} else {
|
||||
speed = pf.walkSpeed;
|
||||
setLocomotionState(e, "walk");
|
||||
}
|
||||
} else {
|
||||
setLocomotionState(e, "walk");
|
||||
}
|
||||
|
||||
cc.linearVelocity = toTarget * speed;
|
||||
|
||||
// Rotate character to face movement
|
||||
// direction (Y plane only)
|
||||
rotateTowards(e, toTarget, deltaTime);
|
||||
} else {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
}
|
||||
return;
|
||||
toTarget = targetPos - charPos;
|
||||
}
|
||||
|
||||
// --- State: Executing ---
|
||||
if (state.state == State::Executing) {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
toTarget.y = 0;
|
||||
if (toTarget.squaredLength() > 0.0001f) {
|
||||
toTarget.normalise();
|
||||
|
||||
// When the behavior tree is running (via ActionDebug),
|
||||
// do NOT override animation states - the behavior tree's
|
||||
// setAnimationState nodes should be the sole controller.
|
||||
if (!debug.isRunning ||
|
||||
debug.currentActionName.empty()) {
|
||||
setLocomotionState(e, "idle");
|
||||
float distToTarget =
|
||||
(charPos - objPos).length();
|
||||
float speed = 2.5f;
|
||||
if (e.has<PathFollowingComponent>()) {
|
||||
auto &pf =
|
||||
e.get<PathFollowingComponent>();
|
||||
speed = pf.walkSpeed;
|
||||
if (distToTarget >
|
||||
pf.walkSpeed * 3.0f) {
|
||||
speed = pf.runSpeed;
|
||||
setLocomotionState(e, "run");
|
||||
} else {
|
||||
speed = pf.walkSpeed;
|
||||
setLocomotionState(e, "walk");
|
||||
}
|
||||
} else {
|
||||
setLocomotionState(e, "walk");
|
||||
}
|
||||
|
||||
if (state.target.isExecuting && db) {
|
||||
const GoapAction *action =
|
||||
db->findAction(
|
||||
state.target.actionName);
|
||||
if (action) {
|
||||
// Kick off the action's
|
||||
// behavior tree via
|
||||
// BehaviorTreeSystem's
|
||||
// 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;
|
||||
// When root motion is enabled, the
|
||||
// AnimationTreeSystem already computes
|
||||
// linearVelocity from animation root
|
||||
// bone displacement. Do NOT overwrite
|
||||
// it with direct movement velocity.
|
||||
if (!hasRootMotion(e))
|
||||
cc.linearVelocity = toTarget * speed;
|
||||
|
||||
// Check if the behavior tree
|
||||
// has finished (sequence
|
||||
// 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 (debug.runTimer > 0.0f &&
|
||||
btState.treeResult !=
|
||||
BehaviorTreeSystem::
|
||||
Status::running) {
|
||||
// Behavior tree
|
||||
// completed - stop
|
||||
// evaluation
|
||||
debug.isRunning = false;
|
||||
debug.currentActionName
|
||||
.clear();
|
||||
// Rotate character to face movement
|
||||
// direction (Y plane only)
|
||||
rotateTowards(e, toTarget, deltaTime);
|
||||
} else if (!hasRootMotion(e)) {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bb.apply(
|
||||
action->effects);
|
||||
state.state =
|
||||
State::Idle;
|
||||
state.target
|
||||
.isExecuting =
|
||||
false;
|
||||
debug.lastResult =
|
||||
"Smart object action '" +
|
||||
state.target
|
||||
.actionName +
|
||||
"' completed";
|
||||
}
|
||||
} else {
|
||||
// --- State: Executing ---
|
||||
if (state.state == State::Executing) {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
|
||||
// When the behavior tree is running (via ActionDebug),
|
||||
// do NOT override animation states - the behavior tree's
|
||||
// setAnimationState nodes should be the sole controller.
|
||||
if (!debug.isRunning ||
|
||||
debug.currentActionName.empty()) {
|
||||
setLocomotionState(e, "idle");
|
||||
}
|
||||
|
||||
if (state.target.isExecuting && db) {
|
||||
const GoapAction *action =
|
||||
db->findAction(state.target.actionName);
|
||||
if (action) {
|
||||
// Kick off the action's
|
||||
// behavior tree via
|
||||
// BehaviorTreeSystem's
|
||||
// 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).
|
||||
// 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 (debug.runTimer > 0.0f &&
|
||||
btState.treeResult !=
|
||||
BehaviorTreeSystem::Status::
|
||||
running) {
|
||||
// Behavior tree
|
||||
// completed - stop
|
||||
// evaluation
|
||||
debug.isRunning = false;
|
||||
debug.currentActionName.clear();
|
||||
|
||||
bb.apply(action->effects);
|
||||
state.state = State::Idle;
|
||||
state.target.isExecuting =
|
||||
false;
|
||||
debug.lastResult =
|
||||
"Smart object action '" +
|
||||
state.target.actionName +
|
||||
"' completed";
|
||||
}
|
||||
} else {
|
||||
state.state = State::Idle;
|
||||
state.target.isExecuting = false;
|
||||
}
|
||||
} else {
|
||||
state.state = State::Idle;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user