From a6b79fb14cce996288d6cb23d180fc52a88a9199 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Tue, 9 Jan 2024 22:01:16 +0100 Subject: [PATCH] Rudimentary behaviour tree visualization --- engine/breeze/ai/behaviour_tree.c | 81 ++++++++++++++++++++++++++-- engine/breeze/ai/behaviour_tree.h | 23 +++++++- engine/tests/btree_test.c | 87 +++++++++++++++++++++++++------ 3 files changed, 171 insertions(+), 20 deletions(-) diff --git a/engine/breeze/ai/behaviour_tree.c b/engine/breeze/ai/behaviour_tree.c index 46f0060..a5a1de2 100644 --- a/engine/breeze/ai/behaviour_tree.c +++ b/engine/breeze/ai/behaviour_tree.c @@ -24,6 +24,7 @@ struct BzAIBTNode { } delay; struct { BzAIBTActionFn fn; + const char *name; } action; } as; @@ -55,6 +56,37 @@ size_t bzAIBTGetNodeStateSize() { return sizeof(BzAIBTNodeState); } +const char *bzAIBTNodeTypeToStr(BzAIBTNodeType type) { + switch (type) { + case BZ_AIBT_COMP_SELECTOR: + return "SELECTOR"; + case BZ_AIBT_COMP_SEQUENCE: + return "SEQUENCE"; + case BZ_AIBT_COMP_PARALLEL_SELECTOR: + return "PARALLEL_SELECTOR"; + case BZ_AIBT_COMP_PARALLEL_SEQUENCE: + return "PARALLEL_SEQUENCE"; + case BZ_AIBT_DECOR_DUMMY: + return "DUMMY"; + case BZ_AIBT_DECOR_SUCCESS: + return "SUCCESS"; + case BZ_AIBT_DECOR_FAIL: + return "FAIL"; + case BZ_AIBT_DECOR_INVERT: + return "INVERT"; + case BZ_AIBT_DECOR_UNTIL_SUCCESS: + return "UNTIL_SUCCESS"; + case BZ_AIBT_DECOR_UNTIL_FAIL: + return "UNTIL_FAIL"; + case BZ_AIBT_DECOR_REPEAT: + return "REPEAT"; + case BZ_AIBT_DECOR_DELAY: + return "DELAY"; + case BZ_AIBT_ACTION: + return "ACTION"; + } +} + static BzAIBTNode *bzAIBTNodeMake(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTNodeType type) { BZ_ASSERT(nodePool); BZ_ASSERT(bzObjectPoolGetObjectSize(nodePool) == bzAIBTGetNodeSize()); @@ -135,15 +167,58 @@ BzAIBTNode *bzAIBTDecorDelay(BzObjectPool *nodePool, BzAIBTNode *parent, f32 ms) return node; } -BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn) { +BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn, + const char *name) { BzAIBTNode *node = bzAIBTNodeMake(nodePool, parent, BZ_AIBT_ACTION); node->as.action.fn = fn; + node->as.action.name = name; return node; } -BzAIBTNodeType bzAIBTGetNodeType(BzAIBTNode *node) { +i32 bzAIBTDecorGetRepeat(const BzAIBTNode *node) { + BZ_ASSERT(node->type == BZ_AIBT_DECOR_REPEAT); + return node->as.repeat.n; +} +f32 bzAIBTDecorGetDelay(const BzAIBTNode *node) { + BZ_ASSERT(node->type == BZ_AIBT_DECOR_DELAY); + return node->as.delay.ms; +} + +BzAIBTActionFn bzAIBTActionGetFn(const BzAIBTNode *node) { + BZ_ASSERT(node->type == BZ_AIBT_ACTION); + return node->as.action.fn; +} +const char *bzAIBTActionGetName(const BzAIBTNode *node) { + BZ_ASSERT(node->type == BZ_AIBT_ACTION); + return node->as.action.name; +} + +BzAIBTNodeType bzAIBTGetNodeType(const BzAIBTNode *node) { return node->type; } +BzAIBTNode *bzAIBTNodeChild(const BzAIBTNode *node) { + return node->first; +} +BzAIBTNode *bzAIBTNodeNext(const BzAIBTNode *node) { + return node->next; +} + +const BzAIBTNodeState *bzAIBTNodeStateNext(const BzAIBTNodeState *state) { + BZ_ASSERT(state); + return state->next; +} +bool bzAIBTNodeMatchesState(const BzAIBTNode *node, const BzAIBTNodeState *state) { + return state && state->node == node; +} + +i32 bzAIBTRepeatStateGetIter(const BzAIBTNodeState *state) { + BZ_ASSERT(state->node && state->node->type == BZ_AIBT_DECOR_REPEAT); + return state->as.repeat.iter; +} +f32 bzAIBTDelayStateGetElapsed(const BzAIBTNodeState *state) { + BZ_ASSERT(state->node && state->node->type == BZ_AIBT_DECOR_DELAY); + return state->as.delay.elapsed; +} BzAIBTState bzAIBTCreateState(const BzAIBTStateDesc *desc) { BZ_ASSERT(desc->pool); @@ -205,7 +280,7 @@ void bzAIBTStateRelease(BzAIBTState *state, BzAIBTNodeState *nodeState) { } bool nodeMatchesState(const BzAIBTNode *node, const BzAIBTNodeState *state) { - return state && state->node == node; + return bzAIBTNodeMatchesState(node, state); } BzAIBTNodeState *getNextNodeState(const BzAIBTNode *node, BzAIBTNodeState *nodeState) { if (nodeState && nodeMatchesState(node, nodeState)) diff --git a/engine/breeze/ai/behaviour_tree.h b/engine/breeze/ai/behaviour_tree.h index ff7cf14..8f6016b 100644 --- a/engine/breeze/ai/behaviour_tree.h +++ b/engine/breeze/ai/behaviour_tree.h @@ -55,6 +55,8 @@ typedef struct BzAIBTStateDesc { size_t bzAIBTGetNodeSize(); size_t bzAIBTGetNodeStateSize(); +const char *bzAIBTNodeTypeToStr(BzAIBTNodeType type); + BzAIBTNode *bzAIBTMakeRoot(BzObjectPool *nodePool); void bzAIBTDestroyRoot(BzObjectPool *nodePool, BzAIBTNode *node); @@ -70,9 +72,26 @@ BzAIBTNode *bzAIBTDecorUntilFail(BzObjectPool *nodePool, BzAIBTNode *parent); BzAIBTNode *bzAIBTDecorRepeat(BzObjectPool *nodePool, BzAIBTNode *parent, i32 n); BzAIBTNode *bzAIBTDecorDelay(BzObjectPool *nodePool, BzAIBTNode *parent, f32 ms); -BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn); +BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn, + const char *name); -BzAIBTNodeType bzAIBTGetNodeType(BzAIBTNode *node); +// Reflection data + +i32 bzAIBTDecorGetRepeat(const BzAIBTNode *node); +f32 bzAIBTDecorGetDelay(const BzAIBTNode *node); + +BzAIBTActionFn bzAIBTActionGetFn(const BzAIBTNode *node); +const char *bzAIBTActionGetName(const BzAIBTNode *node); + +BzAIBTNodeType bzAIBTGetNodeType(const BzAIBTNode *node); +BzAIBTNode *bzAIBTNodeChild(const BzAIBTNode *node); +BzAIBTNode *bzAIBTNodeNext(const BzAIBTNode *node); + +const BzAIBTNodeState *bzAIBTNodeStateNext(const BzAIBTNodeState *state); +bool bzAIBTNodeMatchesState(const BzAIBTNode *node, const BzAIBTNodeState *state); + +i32 bzAIBTRepeatStateGetIter(const BzAIBTNodeState *state); +f32 bzAIBTDelayStateGetElapsed(const BzAIBTNodeState *state); BzAIBTState bzAIBTCreateState(const BzAIBTStateDesc *desc); void bzAIBTDestroyState(BzAIBTState *state); diff --git a/engine/tests/btree_test.c b/engine/tests/btree_test.c index c199e9e..f3613a3 100644 --- a/engine/tests/btree_test.c +++ b/engine/tests/btree_test.c @@ -5,6 +5,7 @@ BzObjectPool *nodePool = NULL; BzObjectPool *nodeStatePool = NULL; BzAIBTNode *printBT = NULL; +BzAIBTState agentState; BzAIBTStatus printAction(void *data) { bzLogInfo("Hello, world!"); @@ -12,6 +13,7 @@ BzAIBTStatus printAction(void *data) { } bool init(int *game) { + rlImGuiSetup(true); nodePool = bzObjectPoolCreate(&(BzObjectPoolDesc) { .objectSize = bzAIBTGetNodeSize(), }); @@ -29,40 +31,95 @@ bool init(int *game) { BzAIBTNode *seq = bzAIBTCompSequence(nodePool, node, false); bzAIBTDecorDelay(nodePool, seq, 1.0f); - bzAIBTAction(nodePool, seq, printAction); + bzAIBTAction(nodePool, seq, printAction, "printAction"); - BzAIBTState state = bzAIBTCreateState(&(BzAIBTStateDesc) { + agentState = bzAIBTCreateState(&(BzAIBTStateDesc) { .root = printBT, .pool = nodeStatePool, .userData = NULL }); - BzAIBTStatus status = BZ_AIBT_RUNNING; - - i32 count = 0; - while (status == BZ_AIBT_RUNNING) { - status = bzAIBTExecute(&state, 0.2f); - count++; - assert(status != BZ_AIBT_ERROR); - } - bzLogInfo("Iter: %d", count); + return true; +} +void deinit(int *game) { bzObjectPoolDestroy(nodePool); bzObjectPoolDestroy(nodeStatePool); +} - return true; +void igRenderBTNode(const BzAIBTNode *node, const BzAIBTNodeState *state, i32 depth) { + const BzAIBTNode *child = bzAIBTNodeChild(node); + BzAIBTNodeType type = bzAIBTGetNodeType(node); + char extraInfo[128]; + extraInfo[0] = '\0'; + + bool hasState = bzAIBTNodeMatchesState(node, state); + + switch (type) { + case BZ_AIBT_DECOR_REPEAT: + if (hasState) { + snprintf(extraInfo, sizeof(extraInfo), "(%d < %d)", + bzAIBTRepeatStateGetIter(state), + bzAIBTDecorGetRepeat(node)); + } else { + snprintf(extraInfo, sizeof(extraInfo), "(%d)", bzAIBTDecorGetRepeat(node)); + } + break; + case BZ_AIBT_DECOR_DELAY: + if (hasState) { + snprintf(extraInfo, sizeof(extraInfo), "(%.2f < %.2fms)", + bzAIBTDelayStateGetElapsed(state), + bzAIBTDecorGetDelay(node)); + } else { + snprintf(extraInfo, sizeof(extraInfo), "(%.2fms)", bzAIBTDecorGetDelay(node)); + } + break; + case BZ_AIBT_ACTION: + snprintf(extraInfo, sizeof(extraInfo), "(%s:%p)", + bzAIBTActionGetName(node), + bzAIBTActionGetFn(node)); + break; + default: + break; + } + if (hasState) + state = bzAIBTNodeStateNext(state); + + ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f}; + if (hasState) + color = (ImVec4) {1.0f, 1.0f, 0.5f, 1.0f}; + + igTextColored(color, "%*s%s %s", depth * 4, "", + bzAIBTNodeTypeToStr(type), extraInfo); + while (child) { + igRenderBTNode(child, state, depth + 1); + child = bzAIBTNodeNext(child); + } +} + +void igRenderBT(BzAIBTState *state) { + const BzAIBTNode *root = state->root; + if (igBegin("BehaviourTree", NULL, 0)) { + igRenderBTNode(root, state->first, 0); + } + igEnd(); } void render(float dt, int *game) { ClearBackground(WHITE); + BzAIBTStatus status = bzAIBTExecute(&agentState, dt); + + rlImGuiBegin(); + igRenderBT(&agentState); + igShowDemoWindow(NULL); + rlImGuiEnd(); } bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { appDesc->init = (BzAppInitFunc) init; + appDesc->deinit = (BzAppDeinitFunc ) deinit; appDesc->render = (BzAppRenderFunc) render; - init(NULL); - - return false; + return true; }