From 25a3229bb20beb768eef7ffc9381521bcb033079 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Thu, 11 Jan 2024 09:33:51 +0100 Subject: [PATCH] Polish up behaviour tree, add docs --- engine/breeze/ai/behaviour_tree.c | 44 ++++---- engine/breeze/ai/behaviour_tree.h | 169 ++++++++++++++++++++++++++++-- engine/tests/btree_test.c | 8 +- game/components.c | 4 +- game/main.c | 4 +- 5 files changed, 192 insertions(+), 37 deletions(-) diff --git a/engine/breeze/ai/behaviour_tree.c b/engine/breeze/ai/behaviour_tree.c index f8ec0d6..80c35d4 100644 --- a/engine/breeze/ai/behaviour_tree.c +++ b/engine/breeze/ai/behaviour_tree.c @@ -61,9 +61,9 @@ const char *bzBTNodeTypeToStr(BzBTNodeType type) { return "SELECTOR"; case BZ_BT_COMP_SEQUENCE: return "SEQUENCE"; - case BZ_BT_COMP_P_SELECTOR: + case BZ_BT_COMP_PSELECTOR: return "P_SELECTOR"; - case BZ_BT_COMP_P_SEQUENCE: + case BZ_BT_COMP_PSEQUENCE: return "P_SEQUENCE"; case BZ_BT_DECOR_DUMMY: return "DUMMY"; @@ -124,17 +124,17 @@ void bzBTDestroyRoot(BzObjectPool *nodePool, BzBTNode *node) { bzObjectPoolRelease(nodePool, node); } -BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) { - BzBTNodeType type = parallel ? - BZ_BT_COMP_P_SELECTOR : - BZ_BT_COMP_SELECTOR; - return bzBTNodeMake(nodePool, parent, type); +BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent) { + return bzBTNodeMake(nodePool, parent, BZ_BT_COMP_SELECTOR); } -BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) { - BzBTNodeType type = parallel ? - BZ_BT_COMP_P_SEQUENCE : - BZ_BT_COMP_SEQUENCE; - return bzBTNodeMake(nodePool, parent, type); +BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent) { + return bzBTNodeMake(nodePool, parent, BZ_BT_COMP_SEQUENCE); +} +BzBTNode *bzBTCompPSelector(BzObjectPool *nodePool, BzBTNode *parent) { + return bzBTNodeMake(nodePool, parent, BZ_BT_COMP_PSELECTOR); +} +BzBTNode *bzBTCompPSequence(BzObjectPool *nodePool, BzBTNode *parent) { + return bzBTNodeMake(nodePool, parent, BZ_BT_COMP_PSEQUENCE); } BzBTNode *bzBTDecorDummy(BzObjectPool *nodePool, BzBTNode *parent) { @@ -220,9 +220,9 @@ BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state) { BZ_ASSERT(state->node); BzBTNodeType type = state->node->type; bool isComposite = type == BZ_BT_COMP_SELECTOR || - type == BZ_BT_COMP_P_SELECTOR || + type == BZ_BT_COMP_PSELECTOR || type == BZ_BT_COMP_SEQUENCE || - type == BZ_BT_COMP_P_SEQUENCE; + type == BZ_BT_COMP_PSEQUENCE; BZ_ASSERT(isComposite); return state->as.composite.running; } @@ -384,8 +384,8 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, BzBTExecState *state, BzObjectPool *statePool) { BzBTNodeState *nodeState = getNodeState(node, state); - bool isParallel = node->type == BZ_BT_COMP_P_SEQUENCE || - node->type == BZ_BT_COMP_P_SELECTOR; + bool isParallel = node->type == BZ_BT_COMP_PSEQUENCE || + node->type == BZ_BT_COMP_PSELECTOR; BzBTNode *start = node->first; if (!isParallel && nodeState) @@ -419,12 +419,12 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, } switch (node->type) { case BZ_BT_COMP_SELECTOR: - case BZ_BT_COMP_P_SELECTOR: + case BZ_BT_COMP_PSELECTOR: if (childReturn.status == BZ_BT_SUCCESS) execReturn.status = BZ_BT_SUCCESS; break; case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_P_SEQUENCE: + case BZ_BT_COMP_PSEQUENCE: if (childReturn.status == BZ_BT_FAIL) execReturn.status = BZ_BT_FAIL; break; @@ -441,12 +441,12 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, } switch (node->type) { case BZ_BT_COMP_SELECTOR: - case BZ_BT_COMP_P_SELECTOR: + case BZ_BT_COMP_PSELECTOR: if (numFailed == numChildren) execReturn.status = BZ_BT_FAIL; break; case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_P_SEQUENCE: + case BZ_BT_COMP_PSEQUENCE: if (numSuccessful == numChildren) execReturn.status = BZ_BT_SUCCESS; break; @@ -585,8 +585,8 @@ static inline BzBTExecReturn bzBTExecuteNode(const BzBTNode *node, f32 dt, switch (node->type) { case BZ_BT_COMP_SELECTOR: case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_P_SELECTOR: - case BZ_BT_COMP_P_SEQUENCE: + case BZ_BT_COMP_PSELECTOR: + case BZ_BT_COMP_PSEQUENCE: execReturn = bzBTExecuteComposite(node, dt, state, statePool); break; case BZ_BT_DECOR_DUMMY: diff --git a/engine/breeze/ai/behaviour_tree.h b/engine/breeze/ai/behaviour_tree.h index 600559b..dec6ccb 100644 --- a/engine/breeze/ai/behaviour_tree.h +++ b/engine/breeze/ai/behaviour_tree.h @@ -18,8 +18,8 @@ typedef enum BzBTNodeType { // Composite BZ_BT_COMP_SELECTOR, BZ_BT_COMP_SEQUENCE, - BZ_BT_COMP_P_SELECTOR, - BZ_BT_COMP_P_SEQUENCE, + BZ_BT_COMP_PSELECTOR, + BZ_BT_COMP_PSEQUENCE, // Decorator BZ_BT_DECOR_DUMMY, BZ_BT_DECOR_SUCCESS, @@ -56,26 +56,166 @@ typedef struct BzBTStateDesc { void *userData; } BzBTStateDesc; +/** + * @brief Useful for allocating BzBTNode pools. + * @return size of BzBTNode + */ size_t bzBTGetNodeSize(); +/** + * @brief Useful for allocating BzBTNodeState pools. + * @return size of BzBTNodeState + */ size_t bzBTGetNodeStateSize(); +/** + * @brief Utility function for converting enum type to human readable string. + * @return human-readable node type + */ const char *bzBTNodeTypeToStr(BzBTNodeType type); +/** + * @brief Starting point of BT. + * @param nodePool pool + * @return allocated node + */ BzBTNode *bzBTMakeRoot(BzObjectPool *nodePool); -void bzBTDestroyRoot(BzObjectPool *nodePool, BzBTNode *node); +/** + * @brief Recursively cleans up BT. + * @param nodePool pool + * @param node BT root + */ +void bzBTDestroyRoot(BzObjectPool *nodePool, BzBTNode *node); -BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent, bool parallel); -BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent, bool parallel); +/** + * @brief Execute all children in turn until one fails. + * + * Execution is BZ_BT_SUCCESS, if all children are ran successfully. + * Child execution, does not progress unless child finishes (no BZ_BT_RUNNING status). + * + * @param nodePool pool + * @param parent parent of the composite + * @return allocated composite + */ +BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Execute all children in turn until first one succeeds. + * + * Execution is BZ_BT_FAIL, if no children are ran successfully. + * Child execution is blocking -> it does not progress unless child + * finishes (is not BZ_BT_RUNNING). + * + * @param nodePool pool + * @param parent parent of the composite + * @return allocated composite + */ +BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Execute all children in turn until one fails. + * + * Execution is BZ_BT_SUCCESS, if all children are ran successfully. + * Children are executed in non-blocking way in the order of BT. + * + * @param nodePool pool + * @param parent parent of the composite + * @return allocated composite + */ +BzBTNode *bzBTCompPSelector(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Execute all children in turn until first one succeeds. + * + * Execution is BZ_BT_FAIL, if no children are ran successfully. + * Children are executed in non-blocking way in the order of BT. + * + * @param nodePool pool + * @param parent parent of the composite + * @return allocated composite + */ +BzBTNode *bzBTCompPSequence(BzObjectPool *nodePool, BzBTNode *parent); + +/** + * @brief Returns child status. + * + * Note: Useful when nesting BTs. + * + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorDummy(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Returns BZ_BT_SUCCESS, if no BZ_BT_ERROR occurred. + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorSuccess(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Returns BZ_BT_FAIL, if no BZ_BT_ERROR occurred. + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorFail(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Inverts child result. + * + * Note: Only for BZ_BT_SUCCESS and BZ_BT_FAIL. Other statuses + * are propagated up. + * + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorInvert(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Repeats child execution until it returns BZ_BT_SUCCESS. + * + * Note: BZ_BT_ERROR also causes it to return. + * + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorUntilSuccess(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Repeats child execution until it returns BZ_BT_FAIL. + * + * Note: BZ_BT_ERROR also causes it to return. + * + * @param nodePool pool + * @param parent parent of the decorator + * @return allocated decorator + */ BzBTNode *bzBTDecorUntilFail(BzObjectPool *nodePool, BzBTNode *parent); +/** + * @brief Repeats child execution n-times. + * @param nodePool pool + * @param parent parent of the decorator + * @param n number of repeats + * @return allocated decorator + */ BzBTNode *bzBTDecorRepeat(BzObjectPool *nodePool, BzBTNode *parent, i32 n); +/** + * @brief Delays child/sibling execution for specified ms. + * + * Note: for this to properly work, correct dt must be passed + * in when executing BT. + * + * @param nodePool pool + * @param parent parent of the decorator + * @param ms milliseconds to wait + * @return allocated decorator + */ BzBTNode *bzBTDecorDelay(BzObjectPool *nodePool, BzBTNode *parent, f32 ms); +/** + * @brief Creates BT task. + * @param nodePool pool + * @param parent parent of the task + * @param fn function pointer for the task + * @return allocated action + */ BzBTNode *bzBTAction(BzObjectPool *nodePool, BzBTNode *parent, BzBTActionFn fn); // Reflection data @@ -102,9 +242,24 @@ BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state); i32 bzBTRepeatStateGetIter(const BzBTNodeState *state); f32 bzBTDelayStateGetElapsed(const BzBTNodeState *state); -BzBTState bzBTCreateState(const BzBTStateDesc *desc); -void bzBTDestroyState(BzBTState *state); +/** + * @brief Creates state for a given BT. + * @param desc input parameters + * @return BT state + */ +BzBTState bzBTCreateState(const BzBTStateDesc *desc); +/** + * @brief Cleans up state (does not modify BT). + * @param state state to clean up + */ +void bzBTDestroyState(BzBTState *state); +/** + * @brief Tick behaviour tree. + * @param state state of the behaviour tree + * @param dt delta time + * @return status of BT tick + */ BzBTStatus bzBTExecute(BzBTState *state, f32 dt); #endif //BREEZE_BEHAVIOUR_TREE_H diff --git a/engine/tests/btree_test.c b/engine/tests/btree_test.c index 756819e..e5a7b9f 100644 --- a/engine/tests/btree_test.c +++ b/engine/tests/btree_test.c @@ -26,12 +26,12 @@ bool init(int *game) { // delay 1s // print "Hello, world!" printBT = bzBTMakeRoot(nodePool); - BzBTNode *pseq = bzBTCompSelector(nodePool, printBT, true); + BzBTNode *pseq = bzBTCompPSelector(nodePool, printBT); bzBTDecorDelay(nodePool, pseq, 2.0f); BzBTNode *node = bzBTDecorRepeat(nodePool, pseq, 5); - BzBTNode *seq = bzBTCompSequence(nodePool, node, false); + BzBTNode *seq = bzBTCompSequence(nodePool, node); bzBTDecorDelay(nodePool, seq, 1.0f); node = bzBTAction(nodePool, seq, printAction); @@ -155,9 +155,9 @@ void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, } bool isComposite = type == BZ_BT_COMP_SELECTOR || - type == BZ_BT_COMP_P_SELECTOR || + type == BZ_BT_COMP_PSELECTOR || type == BZ_BT_COMP_SEQUENCE || - type == BZ_BT_COMP_P_SEQUENCE; + type == BZ_BT_COMP_PSEQUENCE; while (child) { if (hasSingleChild) igSameLine(0, 0); diff --git a/game/components.c b/game/components.c index 01c703a..2164dbc 100644 --- a/game/components.c +++ b/game/components.c @@ -279,9 +279,9 @@ static void visualizeBTState(const BzBTNode *node, const BzBTNodeState *state, b igTextColored(color, "%s%*s%s%s%s", prefix, offset, "", bzBTNodeTypeToStr(type), extraInfo, postfix); bool isComposite = type == BZ_BT_COMP_SELECTOR || - type == BZ_BT_COMP_P_SELECTOR || + type == BZ_BT_COMP_PSELECTOR || type == BZ_BT_COMP_SEQUENCE || - type == BZ_BT_COMP_P_SEQUENCE; + type == BZ_BT_COMP_PSEQUENCE; depth++; while (child) { if (hasSingleChild) igSameLine(0, 0); diff --git a/game/main.c b/game/main.c index 28602d9..0706a62 100644 --- a/game/main.c +++ b/game/main.c @@ -260,11 +260,11 @@ bool init(void *userData) { //node = bzBTDecorUntilFail(nodePool, root); - BzBTNode *collectSeq = bzBTCompSequence(nodePool, root, false); + BzBTNode *collectSeq = bzBTCompSequence(nodePool, root); BzBTNode *untilFail = bzBTDecorUntilFail(nodePool, collectSeq); { - BzBTNode *untilSeq = bzBTCompSequence(nodePool, untilFail, false); + BzBTNode *untilSeq = bzBTCompSequence(nodePool, untilFail); node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiFindNextHarvestable); bzBTNodeSetName(node, "findNextHarvestable"); node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiMoveTo);