Delays and animation conflicts fixed
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
* "setValue" - Leaf: sets blackboard value (name=key, params=val)
|
||||
* "checkValue" - Leaf check: blackboard comparison (name=key, params="op val")
|
||||
* "blackboardDump" - Leaf: dumps entire blackboard to log
|
||||
* "delay" - Leaf: waits for N seconds (params=seconds as float)
|
||||
*/
|
||||
struct BehaviorTreeNode {
|
||||
Ogre::String type = "task";
|
||||
@@ -61,7 +62,8 @@ struct BehaviorTreeNode {
|
||||
type == "debugPrint" || type == "setAnimationState" ||
|
||||
type == "isAnimationEnded" || type == "setBit" ||
|
||||
type == "checkBit" || type == "setValue" ||
|
||||
type == "checkValue" || type == "blackboardDump";
|
||||
type == "checkValue" || type == "blackboardDump" ||
|
||||
type == "delay";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ static bool parseBitIndex(const Ogre::String &str, int &out)
|
||||
}
|
||||
|
||||
static bool parseValueString(const Ogre::String &str, int &outInt,
|
||||
float &outFloat, Ogre::Vector3 &outVec3,
|
||||
int &type)
|
||||
float &outFloat, Ogre::Vector3 &outVec3, int &type)
|
||||
{
|
||||
// type: 0=int, 1=float, 2=vec3
|
||||
if (str.find(',') != Ogre::String::npos) {
|
||||
@@ -76,26 +75,25 @@ static bool parseComparison(const Ogre::String &str, Comparison &out)
|
||||
s++;
|
||||
|
||||
const char *ops[] = { "==", "!=", ">=", "<=", ">", "<" };
|
||||
Comparison::Op opTypes[] = {
|
||||
Comparison::Eq, Comparison::Ne, Comparison::Ge,
|
||||
Comparison::Le, Comparison::Gt, Comparison::Lt
|
||||
};
|
||||
Comparison::Op opTypes[] = { Comparison::Eq, Comparison::Ne,
|
||||
Comparison::Ge, Comparison::Le,
|
||||
Comparison::Gt, Comparison::Lt };
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
size_t len = strlen(ops[i]);
|
||||
if (strncmp(s, ops[i], len) == 0) {
|
||||
out.op = opTypes[i];
|
||||
Ogre::String valStr(s + len);
|
||||
return parseValueString(valStr, out.intVal, out.floatVal,
|
||||
out.vec3Val, out.valueType);
|
||||
return parseValueString(valStr, out.intVal,
|
||||
out.floatVal, out.vec3Val,
|
||||
out.valueType);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool compareValues(const Comparison &cmp, int actualInt,
|
||||
float actualFloat,
|
||||
const Ogre::Vector3 &actualVec3,
|
||||
float actualFloat, const Ogre::Vector3 &actualVec3,
|
||||
int actualType)
|
||||
{
|
||||
// If types differ, promote int to float, or fail for vec3
|
||||
@@ -123,9 +121,10 @@ static bool compareValues(const Comparison &cmp, int actualInt,
|
||||
a = actualFloat;
|
||||
b = cmp.floatVal;
|
||||
} else if (type == 2) {
|
||||
bool eq = (std::abs(actualVec3.x - cmp.vec3Val.x) < g_epsilon) &&
|
||||
(std::abs(actualVec3.y - cmp.vec3Val.y) < g_epsilon) &&
|
||||
(std::abs(actualVec3.z - cmp.vec3Val.z) < g_epsilon);
|
||||
bool eq =
|
||||
(std::abs(actualVec3.x - cmp.vec3Val.x) < g_epsilon) &&
|
||||
(std::abs(actualVec3.y - cmp.vec3Val.y) < g_epsilon) &&
|
||||
(std::abs(actualVec3.z - cmp.vec3Val.z) < g_epsilon);
|
||||
switch (cmp.op) {
|
||||
case Comparison::Eq:
|
||||
return eq;
|
||||
@@ -170,8 +169,7 @@ bool BehaviorTreeSystem::isNewlyActive(RunnerState &state,
|
||||
char key[32];
|
||||
snprintf(key, sizeof(key), "%p", (void *)node);
|
||||
return state.firstRun ||
|
||||
state.lastActiveLeaves.find(key) ==
|
||||
state.lastActiveLeaves.end();
|
||||
state.lastActiveLeaves.find(key) == state.lastActiveLeaves.end();
|
||||
}
|
||||
|
||||
void BehaviorTreeSystem::markActive(RunnerState &state,
|
||||
@@ -182,13 +180,13 @@ void BehaviorTreeSystem::markActive(RunnerState &state,
|
||||
state.currentActiveLeaves.insert(key);
|
||||
}
|
||||
|
||||
BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
const BehaviorTreeNode &node, flecs::entity e,
|
||||
RunnerState &state)
|
||||
BehaviorTreeSystem::Status
|
||||
BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
RunnerState &state, float deltaTime)
|
||||
{
|
||||
if (node.type == "sequence") {
|
||||
for (const auto &child : node.children) {
|
||||
Status s = evaluateNode(child, e, state);
|
||||
Status s = evaluateNode(child, e, state, deltaTime);
|
||||
if (s == Status::failure)
|
||||
return Status::failure;
|
||||
if (s == Status::running)
|
||||
@@ -199,7 +197,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
|
||||
if (node.type == "selector") {
|
||||
for (const auto &child : node.children) {
|
||||
Status s = evaluateNode(child, e, state);
|
||||
Status s = evaluateNode(child, e, state, deltaTime);
|
||||
if (s == Status::success)
|
||||
return Status::success;
|
||||
if (s == Status::running)
|
||||
@@ -211,7 +209,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
if (node.type == "invert") {
|
||||
if (node.children.empty())
|
||||
return Status::failure;
|
||||
Status s = evaluateNode(node.children[0], e, state);
|
||||
Status s = evaluateNode(node.children[0], e, state, deltaTime);
|
||||
if (s == Status::success)
|
||||
return Status::failure;
|
||||
if (s == Status::failure)
|
||||
@@ -227,11 +225,38 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
return Status::success;
|
||||
}
|
||||
|
||||
if (node.type == "delay") {
|
||||
char key[32];
|
||||
snprintf(key, sizeof(key), "%p", (void *)&node);
|
||||
|
||||
/* Parse delay duration from params (seconds as float) */
|
||||
float duration = 1.0f; /* default 1 second */
|
||||
if (!node.params.empty()) {
|
||||
char *end = nullptr;
|
||||
float val = strtof(node.params.c_str(), &end);
|
||||
if (end != node.params.c_str() && *end == '\0' &&
|
||||
val > 0.0f)
|
||||
duration = val;
|
||||
}
|
||||
|
||||
/* Initialize timer on first activation */
|
||||
if (isNewlyActive(state, &node))
|
||||
state.nodeTimers[key] = 0.0f;
|
||||
|
||||
/* Accumulate time */
|
||||
state.nodeTimers[key] += deltaTime;
|
||||
|
||||
if (state.nodeTimers[key] >= duration)
|
||||
return Status::success;
|
||||
|
||||
return Status::running;
|
||||
}
|
||||
|
||||
if (node.type == "debugPrint") {
|
||||
if (isNewlyActive(state, &node)) {
|
||||
std::cout << "[BT] " << node.name << std::endl;
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"[BT] " + node.name);
|
||||
Ogre::LogManager::getSingleton().logMessage("[BT] " +
|
||||
node.name);
|
||||
}
|
||||
return Status::success;
|
||||
}
|
||||
@@ -244,8 +269,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
if (slash != Ogre::String::npos) {
|
||||
Ogre::String smName = name.substr(0, slash);
|
||||
Ogre::String stName = name.substr(slash + 1);
|
||||
m_animSystem->setState(e, smName, stName,
|
||||
true);
|
||||
m_animSystem->setState(e, smName, stName, true);
|
||||
}
|
||||
}
|
||||
return Status::success;
|
||||
@@ -261,8 +285,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
return Status::failure;
|
||||
|
||||
/* Find the animation for this state and check time */
|
||||
Ogre::Entity *ent =
|
||||
AnimationTreeSystem::findAnimatedEntity(e);
|
||||
Ogre::Entity *ent = AnimationTreeSystem::findAnimatedEntity(e);
|
||||
if (!ent || !ent->hasSkeleton())
|
||||
return Status::failure;
|
||||
|
||||
@@ -303,12 +326,15 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
auto leaf =
|
||||
st.findAnimationLeaf();
|
||||
if (leaf) {
|
||||
Ogre::AnimationStateSet *
|
||||
set = ent->getAllAnimationStates();
|
||||
Ogre::AnimationStateSet *set =
|
||||
ent->getAllAnimationStates();
|
||||
if (set) {
|
||||
auto it = set->getAnimationStates().find(
|
||||
leaf->animationName);
|
||||
if (it != set->getAnimationStates().end())
|
||||
auto it =
|
||||
set->getAnimationStates()
|
||||
.find(leaf->animationName);
|
||||
if (it !=
|
||||
set->getAnimationStates()
|
||||
.end())
|
||||
as = it->second;
|
||||
}
|
||||
}
|
||||
@@ -411,8 +437,8 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode(
|
||||
if (actualType < 0)
|
||||
return Status::failure;
|
||||
|
||||
bool ok = compareValues(cmp, actualInt, actualFloat,
|
||||
actualVec3, actualType);
|
||||
bool ok = compareValues(cmp, actualInt, actualFloat, actualVec3,
|
||||
actualType);
|
||||
return ok ? Status::success : Status::failure;
|
||||
}
|
||||
|
||||
@@ -434,18 +460,16 @@ void BehaviorTreeSystem::update(float deltaTime)
|
||||
{
|
||||
/* --- BehaviorTreeComponent instances --- */
|
||||
m_world.query<BehaviorTreeComponent>().each(
|
||||
[this](flecs::entity e,
|
||||
BehaviorTreeComponent &bt) {
|
||||
[this, deltaTime](flecs::entity e, BehaviorTreeComponent &bt) {
|
||||
if (!bt.enabled)
|
||||
return;
|
||||
|
||||
auto &state = m_runnerStates[e.id()];
|
||||
state.currentActiveLeaves.clear();
|
||||
|
||||
evaluateNode(bt.root, e, state);
|
||||
evaluateNode(bt.root, e, state, deltaTime);
|
||||
|
||||
state.lastActiveLeaves =
|
||||
state.currentActiveLeaves;
|
||||
state.lastActiveLeaves = state.currentActiveLeaves;
|
||||
state.firstRun = false;
|
||||
});
|
||||
|
||||
@@ -460,10 +484,8 @@ void BehaviorTreeSystem::update(float deltaTime)
|
||||
return;
|
||||
|
||||
m_world.query<ActionDebug>().each(
|
||||
[this, db, deltaTime](flecs::entity e,
|
||||
ActionDebug &debug) {
|
||||
if (!debug.isRunning ||
|
||||
debug.currentActionName.empty())
|
||||
[this, db, deltaTime](flecs::entity e, ActionDebug &debug) {
|
||||
if (!debug.isRunning || debug.currentActionName.empty())
|
||||
return;
|
||||
|
||||
const GoapAction *action =
|
||||
@@ -479,12 +501,14 @@ void BehaviorTreeSystem::update(float deltaTime)
|
||||
if (debug.runTimer <= 0.0f) {
|
||||
state.lastActiveLeaves.clear();
|
||||
state.firstRun = true;
|
||||
state.nodeTimers.clear();
|
||||
state.treeResult = Status::running;
|
||||
}
|
||||
|
||||
evaluateNode(action->behaviorTree, e, state);
|
||||
state.treeResult = evaluateNode(action->behaviorTree, e,
|
||||
state, deltaTime);
|
||||
|
||||
state.lastActiveLeaves =
|
||||
state.currentActiveLeaves;
|
||||
state.lastActiveLeaves = state.currentActiveLeaves;
|
||||
state.firstRun = false;
|
||||
debug.runTimer += deltaTime;
|
||||
});
|
||||
|
||||
@@ -22,16 +22,18 @@ class AnimationTreeSystem;
|
||||
*/
|
||||
class BehaviorTreeSystem {
|
||||
public:
|
||||
BehaviorTreeSystem(flecs::world &world,
|
||||
Ogre::SceneManager *sceneMgr,
|
||||
BehaviorTreeSystem(flecs::world &world, Ogre::SceneManager *sceneMgr,
|
||||
AnimationTreeSystem *animSystem);
|
||||
~BehaviorTreeSystem();
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
/** Result of a single behavior tree evaluation tick. */
|
||||
enum class Status { success, failure, running };
|
||||
|
||||
/** Per-entity state for tracking behavior tree evaluation.
|
||||
* Each entity gets its own RunnerState so the same tree
|
||||
* definition can be shared across multiple characters. */
|
||||
struct RunnerState {
|
||||
/* Tracks which leaf nodes were active last frame,
|
||||
* keyed by node pointer address string */
|
||||
@@ -39,15 +41,36 @@ private:
|
||||
/* Current frame's active leaves */
|
||||
std::unordered_set<std::string> currentActiveLeaves;
|
||||
bool firstRun = true;
|
||||
|
||||
/* Per-node timers for delay nodes, keyed by node pointer
|
||||
* address string. This allows the same tree to be shared
|
||||
* across multiple characters, each with independent timers. */
|
||||
std::unordered_map<std::string, float> nodeTimers;
|
||||
|
||||
/* Result of the root node evaluation from last frame.
|
||||
* Set to Status::running while the tree is active,
|
||||
* Status::success or Status::failure when complete. */
|
||||
Status treeResult = Status::running;
|
||||
};
|
||||
|
||||
Status evaluateNode(const BehaviorTreeNode &node,
|
||||
flecs::entity e,
|
||||
RunnerState &state);
|
||||
/** Access the per-entity runner state for ActionDebug-driven trees.
|
||||
* Used by SmartObjectSystem to detect when a behavior tree
|
||||
* has finished evaluating. */
|
||||
const RunnerState &getActionDebugState(flecs::entity_t id) const
|
||||
{
|
||||
static RunnerState defaultState;
|
||||
auto it = m_actionDebugStates.find(id);
|
||||
if (it != m_actionDebugStates.end())
|
||||
return it->second;
|
||||
return defaultState;
|
||||
}
|
||||
|
||||
private:
|
||||
Status evaluateNode(const BehaviorTreeNode &node, flecs::entity e,
|
||||
RunnerState &state, float deltaTime);
|
||||
|
||||
/* Returns true if this leaf just became active this frame */
|
||||
bool isNewlyActive(RunnerState &state,
|
||||
const BehaviorTreeNode *node);
|
||||
bool isNewlyActive(RunnerState &state, const BehaviorTreeNode *node);
|
||||
|
||||
/* Mark leaf as active for this frame */
|
||||
void markActive(RunnerState &state, const BehaviorTreeNode *node);
|
||||
|
||||
@@ -599,7 +599,14 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
|
||||
auto &state = m_states[e.id()];
|
||||
|
||||
if (state.state == State::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;
|
||||
}
|
||||
@@ -892,8 +899,13 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
if (state.state == State::Executing) {
|
||||
cc.linearVelocity = Ogre::Vector3::ZERO;
|
||||
|
||||
// Set idle animation while executing
|
||||
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()) {
|
||||
setLocomotionState(e, "idle");
|
||||
}
|
||||
|
||||
if (state.target.isExecuting && db) {
|
||||
const GoapAction *action =
|
||||
@@ -908,11 +920,18 @@ void SmartObjectSystem::update(float deltaTime)
|
||||
debug.currentActionName =
|
||||
state.target.actionName;
|
||||
|
||||
state.target.executionTimer +=
|
||||
deltaTime;
|
||||
if (state.target.executionTimer >
|
||||
3.0f) {
|
||||
// Stop behavior tree
|
||||
// Check if the behavior tree
|
||||
// has finished (sequence
|
||||
// completed or failed)
|
||||
auto &btState =
|
||||
m_btSystem
|
||||
->getActionDebugState(
|
||||
e.id());
|
||||
if (btState.treeResult !=
|
||||
BehaviorTreeSystem::Status::
|
||||
running) {
|
||||
// Behavior tree
|
||||
// completed - stop
|
||||
// evaluation
|
||||
debug.isRunning = false;
|
||||
debug.currentActionName
|
||||
|
||||
@@ -19,32 +19,29 @@ static ImU32 nodeTypeColorU32(const Ogre::String &type)
|
||||
return IM_COL32(0x88, 0xDD, 0xFF, 0xFF);
|
||||
if (type == "isAnimationEnded")
|
||||
return IM_COL32(0xDD, 0x88, 0xFF, 0xFF);
|
||||
if (type == "delay")
|
||||
return IM_COL32(0xFF, 0xD7, 0x00, 0xFF);
|
||||
return IM_COL32(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
}
|
||||
|
||||
static void makeLabel(const BehaviorTreeNode &node, char *buf,
|
||||
size_t bufSize)
|
||||
static void makeLabel(const BehaviorTreeNode &node, char *buf, size_t bufSize)
|
||||
{
|
||||
if (node.isLeaf()) {
|
||||
if (node.type == "debugPrint")
|
||||
snprintf(buf, bufSize, "[print] \"%s\"",
|
||||
node.name.empty() ? "" :
|
||||
node.name.c_str());
|
||||
node.name.empty() ? "" : node.name.c_str());
|
||||
else if (node.type == "setAnimationState")
|
||||
snprintf(buf, bufSize, "[anim] %s",
|
||||
node.name.empty() ?
|
||||
"(none)" :
|
||||
node.name.c_str());
|
||||
node.name.empty() ? "(none)" :
|
||||
node.name.c_str());
|
||||
else if (node.type == "isAnimationEnded")
|
||||
snprintf(buf, bufSize, "[ended?] %s",
|
||||
node.name.empty() ?
|
||||
"(none)" :
|
||||
node.name.c_str());
|
||||
node.name.empty() ? "(none)" :
|
||||
node.name.c_str());
|
||||
else
|
||||
snprintf(buf, bufSize, "[%s] %s", node.type.c_str(),
|
||||
node.name.empty() ?
|
||||
"(none)" :
|
||||
node.name.c_str());
|
||||
node.name.empty() ? "(none)" :
|
||||
node.name.c_str());
|
||||
} else {
|
||||
snprintf(buf, bufSize, "[%s]", node.type.c_str());
|
||||
}
|
||||
@@ -118,7 +115,8 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt)
|
||||
if (op.childIndex < vec.size()) {
|
||||
int newIdx = (int)op.childIndex + op.moveDelta;
|
||||
if (newIdx >= 0 && newIdx < (int)vec.size()) {
|
||||
std::swap(vec[op.childIndex], vec[newIdx]);
|
||||
std::swap(vec[op.childIndex],
|
||||
vec[newIdx]);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -134,14 +132,12 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt)
|
||||
void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
|
||||
BehaviorTreeNode *parent,
|
||||
BehaviorTreeNode *&selectedNode,
|
||||
BehaviorTreeNode *&selectedParent,
|
||||
int &id)
|
||||
BehaviorTreeNode *&selectedParent, int &id)
|
||||
{
|
||||
int myId = id++;
|
||||
bool isLeaf = node.isLeaf();
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
if (isLeaf)
|
||||
flags |= ImGuiTreeNodeFlags_Leaf |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||
@@ -151,12 +147,12 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
|
||||
char label[256];
|
||||
makeLabel(node, label, sizeof(label));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImGui::ColorConvertU32ToFloat4(
|
||||
nodeTypeColorU32(node.type)));
|
||||
bool open = ImGui::TreeNodeEx(
|
||||
("##btnode" + std::to_string(myId)).c_str(), flags, "%s",
|
||||
label);
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Text,
|
||||
ImGui::ColorConvertU32ToFloat4(nodeTypeColorU32(node.type)));
|
||||
bool open =
|
||||
ImGui::TreeNodeEx(("##btnode" + std::to_string(myId)).c_str(),
|
||||
flags, "%s", label);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemClicked()) {
|
||||
@@ -194,6 +190,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
|
||||
queueAddChild(&node, "checkValue");
|
||||
if (ImGui::MenuItem("Add Blackboard Dump"))
|
||||
queueAddChild(&node, "blackboardDump");
|
||||
if (ImGui::MenuItem("Add Delay"))
|
||||
queueAddChild(&node, "delay");
|
||||
}
|
||||
if (parent) {
|
||||
size_t idx = findChildIndex(*parent, &node);
|
||||
@@ -239,6 +237,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
|
||||
queueAddChild(&node, "checkValue");
|
||||
if (ImGui::MenuItem("Blackboard Dump"))
|
||||
queueAddChild(&node, "blackboardDump");
|
||||
if (ImGui::MenuItem("Delay"))
|
||||
queueAddChild(&node, "delay");
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
@@ -276,11 +276,20 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Node Properties");
|
||||
|
||||
const char *types[] = { "sequence", "selector", "invert",
|
||||
"task", "check",
|
||||
"debugPrint", "setAnimationState",
|
||||
"isAnimationEnded", "setBit", "checkBit",
|
||||
"setValue", "checkValue", "blackboardDump" };
|
||||
const char *types[] = { "sequence",
|
||||
"selector",
|
||||
"invert",
|
||||
"task",
|
||||
"check",
|
||||
"debugPrint",
|
||||
"setAnimationState",
|
||||
"isAnimationEnded",
|
||||
"setBit",
|
||||
"checkBit",
|
||||
"setValue",
|
||||
"checkValue",
|
||||
"blackboardDump",
|
||||
"delay" };
|
||||
int typeIdx = 0;
|
||||
for (int i = 0; i < IM_ARRAYSIZE(types); i++) {
|
||||
if (node->type == types[i]) {
|
||||
@@ -330,9 +339,10 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
|
||||
|
||||
// Params input for nodes that need it
|
||||
if (node->type == "setBit" || node->type == "setValue" ||
|
||||
node->type == "checkValue") {
|
||||
node->type == "checkValue" || node->type == "delay") {
|
||||
char pbuf[256];
|
||||
snprintf(pbuf, sizeof(pbuf), "%s", node->params.c_str());
|
||||
snprintf(pbuf, sizeof(pbuf), "%s",
|
||||
node->params.c_str());
|
||||
const char *phint = nullptr;
|
||||
if (node->type == "setBit")
|
||||
phint = "1 or 0 (default 1)";
|
||||
@@ -340,6 +350,8 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
|
||||
phint = "Value: 5, 3.14, or 1,2,3";
|
||||
else if (node->type == "checkValue")
|
||||
phint = "Comparison: == 5, > 3.14, != 1,2,3";
|
||||
else if (node->type == "delay")
|
||||
phint = "Duration in seconds (float, default 1.0)";
|
||||
if (ImGui::InputText("Params", pbuf, sizeof(pbuf)))
|
||||
node->params = pbuf;
|
||||
if (phint && ImGui::IsItemHovered())
|
||||
@@ -374,8 +386,7 @@ bool BehaviorTreeEditor::renderComponent(flecs::entity entity,
|
||||
static BehaviorTreeNode *selectedNode = nullptr;
|
||||
static BehaviorTreeNode *selectedParent = nullptr;
|
||||
int id = 0;
|
||||
renderTree(bt.root, nullptr, selectedNode, selectedParent,
|
||||
id);
|
||||
renderTree(bt.root, nullptr, selectedNode, selectedParent, id);
|
||||
|
||||
bool hadEdits = !m_editOps.empty();
|
||||
applyEditOps(bt);
|
||||
|
||||
@@ -20,24 +20,31 @@ void InlineBehaviorTreeEditor::applyOps(BehaviorTreeNode &root)
|
||||
if (op.type == EditOp::Add && op.parent) {
|
||||
if (op.childIndex <= op.parent->children.size())
|
||||
op.parent->children.insert(
|
||||
op.parent->children.begin() + op.childIndex,
|
||||
BehaviorTreeNode{.type = op.addType});
|
||||
op.parent->children.begin() +
|
||||
op.childIndex,
|
||||
BehaviorTreeNode{ .type = op.addType });
|
||||
} else if (op.type == EditOp::Remove && op.parent) {
|
||||
if (op.childIndex < op.parent->children.size())
|
||||
op.parent->children.erase(
|
||||
op.parent->children.begin() + op.childIndex);
|
||||
op.parent->children.begin() +
|
||||
op.childIndex);
|
||||
} else if (op.type == EditOp::Move && op.parent) {
|
||||
if (op.childIndex < op.parent->children.size()) {
|
||||
size_t newIndex = (int)op.childIndex + op.moveDelta;
|
||||
size_t newIndex =
|
||||
(int)op.childIndex + op.moveDelta;
|
||||
if (newIndex >= 0 &&
|
||||
newIndex < op.parent->children.size() &&
|
||||
newIndex != op.childIndex) {
|
||||
auto child = op.parent->children[op.childIndex];
|
||||
auto child =
|
||||
op.parent
|
||||
->children[op.childIndex];
|
||||
op.parent->children.erase(
|
||||
op.parent->children.begin() + op.childIndex);
|
||||
op.parent->children.begin() +
|
||||
op.childIndex);
|
||||
op.parent->children.insert(
|
||||
op.parent->children.begin() + newIndex,
|
||||
child);
|
||||
op.parent->children.begin() +
|
||||
newIndex,
|
||||
child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,27 +72,35 @@ static void makeLabel(const BehaviorTreeNode &node, char *buf, size_t sz)
|
||||
|
||||
static ImVec4 typeColorVec(const char *type)
|
||||
{
|
||||
if (!strcmp(type, "sequence")) return ImVec4(0.53f, 1.0f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "selector")) return ImVec4(1.0f, 1.0f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "invert")) return ImVec4(1.0f, 0.53f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "task")) return ImVec4(0.53f, 0.53f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "check")) return ImVec4(0.53f, 1.0f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "debugPrint")) return ImVec4(1.0f, 0.53f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "setAnimationState")) return ImVec4(1.0f, 0.67f, 0.27f, 1.0f);
|
||||
if (!strcmp(type, "isAnimationEnded")) return ImVec4(0.27f, 1.0f, 0.67f, 1.0f);
|
||||
if (!strcmp(type, "sequence"))
|
||||
return ImVec4(0.53f, 1.0f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "selector"))
|
||||
return ImVec4(1.0f, 1.0f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "invert"))
|
||||
return ImVec4(1.0f, 0.53f, 0.53f, 1.0f);
|
||||
if (!strcmp(type, "task"))
|
||||
return ImVec4(0.53f, 0.53f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "check"))
|
||||
return ImVec4(0.53f, 1.0f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "debugPrint"))
|
||||
return ImVec4(1.0f, 0.53f, 1.0f, 1.0f);
|
||||
if (!strcmp(type, "setAnimationState"))
|
||||
return ImVec4(1.0f, 0.67f, 0.27f, 1.0f);
|
||||
if (!strcmp(type, "isAnimationEnded"))
|
||||
return ImVec4(0.27f, 1.0f, 0.67f, 1.0f);
|
||||
if (!strcmp(type, "delay"))
|
||||
return ImVec4(1.0f, 0.84f, 0.0f, 1.0f);
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
BehaviorTreeNode *parent,
|
||||
BehaviorTreeNode *&selected,
|
||||
int &id)
|
||||
BehaviorTreeNode *&selected, int &id)
|
||||
{
|
||||
int myId = id++;
|
||||
bool isLeaf = node.isLeaf();
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
if (isLeaf)
|
||||
flags |= ImGuiTreeNodeFlags_Leaf |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
||||
@@ -107,18 +122,23 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::MenuItem("Add Child")) {
|
||||
queueOp(EditOp{.type = EditOp::Add,
|
||||
.parent = &node,
|
||||
.childIndex = node.children.size(),
|
||||
.addType = "task"});
|
||||
queueOp(EditOp{ .type = EditOp::Add,
|
||||
.parent = &node,
|
||||
.childIndex = node.children.size(),
|
||||
.addType = "task" });
|
||||
}
|
||||
if (ImGui::MenuItem("Add Delay")) {
|
||||
queueOp(EditOp{ .type = EditOp::Add,
|
||||
.parent = &node,
|
||||
.childIndex = node.children.size(),
|
||||
.addType = "delay" });
|
||||
}
|
||||
if (parent && ImGui::MenuItem("Remove")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{
|
||||
.type = EditOp::Remove,
|
||||
.parent = parent,
|
||||
.childIndex = i});
|
||||
queueOp(EditOp{ .type = EditOp::Remove,
|
||||
.parent = parent,
|
||||
.childIndex = i });
|
||||
if (selected == &node)
|
||||
selected = nullptr;
|
||||
break;
|
||||
@@ -128,10 +148,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (parent && ImGui::MenuItem("Move Up")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{.type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = -1});
|
||||
queueOp(EditOp{ .type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = -1 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -139,10 +159,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (parent && ImGui::MenuItem("Move Down")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{.type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = 1});
|
||||
queueOp(EditOp{ .type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = 1 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -154,10 +174,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (!isLeaf) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("+")) {
|
||||
queueOp(EditOp{.type = EditOp::Add,
|
||||
.parent = &node,
|
||||
.childIndex = node.children.size(),
|
||||
.addType = "task"});
|
||||
queueOp(EditOp{ .type = EditOp::Add,
|
||||
.parent = &node,
|
||||
.childIndex = node.children.size(),
|
||||
.addType = "task" });
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
@@ -165,10 +185,9 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (ImGui::SmallButton("-")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{
|
||||
.type = EditOp::Remove,
|
||||
.parent = parent,
|
||||
.childIndex = i});
|
||||
queueOp(EditOp{ .type = EditOp::Remove,
|
||||
.parent = parent,
|
||||
.childIndex = i });
|
||||
if (selected == &node)
|
||||
selected = nullptr;
|
||||
break;
|
||||
@@ -179,10 +198,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (ImGui::SmallButton("^")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{.type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = -1});
|
||||
queueOp(EditOp{ .type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = -1 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -191,10 +210,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
|
||||
if (ImGui::SmallButton("v")) {
|
||||
for (size_t i = 0; i < parent->children.size(); i++) {
|
||||
if (&parent->children[i] == &node) {
|
||||
queueOp(EditOp{.type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = 1});
|
||||
queueOp(EditOp{ .type = EditOp::Move,
|
||||
.parent = parent,
|
||||
.childIndex = i,
|
||||
.moveDelta = 1 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -213,12 +232,20 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
const char *types[] = {
|
||||
"sequence", "selector", "invert", "task", "check",
|
||||
"debugPrint", "setAnimationState", "isAnimationEnded",
|
||||
"setBit", "checkBit", "setValue", "checkValue",
|
||||
"blackboardDump"
|
||||
};
|
||||
const char *types[] = { "sequence",
|
||||
"selector",
|
||||
"invert",
|
||||
"task",
|
||||
"check",
|
||||
"debugPrint",
|
||||
"setAnimationState",
|
||||
"isAnimationEnded",
|
||||
"setBit",
|
||||
"checkBit",
|
||||
"setValue",
|
||||
"checkValue",
|
||||
"blackboardDump",
|
||||
"delay" };
|
||||
int current = 0;
|
||||
for (int i = 0; i < IM_ARRAYSIZE(types); i++) {
|
||||
if (node->type == types[i]) {
|
||||
@@ -269,9 +296,10 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
|
||||
|
||||
// Params input for nodes that need it
|
||||
if (node->type == "setBit" || node->type == "setValue" ||
|
||||
node->type == "checkValue") {
|
||||
node->type == "checkValue" || node->type == "delay") {
|
||||
char pbuf[256];
|
||||
snprintf(pbuf, sizeof(pbuf), "%s", node->params.c_str());
|
||||
snprintf(pbuf, sizeof(pbuf), "%s",
|
||||
node->params.c_str());
|
||||
const char *phint = nullptr;
|
||||
if (node->type == "setBit")
|
||||
phint = "1 or 0 (default 1)";
|
||||
@@ -279,6 +307,8 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
|
||||
phint = "Value: 5, 3.14, or 1,2,3";
|
||||
else if (node->type == "checkValue")
|
||||
phint = "Comparison: == 5, > 3.14, != 1,2,3";
|
||||
else if (node->type == "delay")
|
||||
phint = "Duration in seconds (float, default 1.0)";
|
||||
if (ImGui::InputText("Params", pbuf, sizeof(pbuf)))
|
||||
node->params = pbuf;
|
||||
if (phint && ImGui::IsItemHovered())
|
||||
@@ -303,15 +333,13 @@ bool InlineBehaviorTreeEditor::render(BehaviorTreeNode &root)
|
||||
int id = 0;
|
||||
getOps().clear();
|
||||
|
||||
if (root.children.empty() && root.type == "task" &&
|
||||
root.name.empty()) {
|
||||
if (root.children.empty() && root.type == "task" && root.name.empty()) {
|
||||
ImGui::TextDisabled("(empty - right-click to add nodes)");
|
||||
if (ImGui::Button("Add Root Child")) {
|
||||
queueOp(EditOp{
|
||||
.type = EditOp::Add,
|
||||
.parent = &root,
|
||||
.childIndex = 0,
|
||||
.addType = "task"});
|
||||
queueOp(EditOp{ .type = EditOp::Add,
|
||||
.parent = &root,
|
||||
.childIndex = 0,
|
||||
.addType = "task" });
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user