Delays and animation conflicts fixed

This commit is contained in:
2026-04-25 20:59:50 +03:00
parent 3bd2801d1d
commit 2cff982473
6 changed files with 272 additions and 165 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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