/tiny_behaviour

Behaviour tree header only library

Primary LanguageC++MIT LicenseMIT

tiny_behaviour

Tiny Behavior is a small, easy to learn and use, header only Behavior Tree library writen in C++.

How To

Its easy, look, there is a simple example:

#include <iostream>
#include "TinyBehavior.h"

int main(int argc, char* argv[]) {
  // all data would be local if we provide appropriate size to the builder
  const size_t treeSize = sizeof(tb::ParallelSequence) + sizeof(tb::Action) * 4 + sizeof(tb::Failer) + sizeof(tb::Condition) + sizeof(tb::Limiter) + sizeof(ActionNode1);
  tb::BehaviorTreeBuilder builder(treeSize);
  tb::BehaviorTree* tree;
  tree = builder.parallel()
                  .action([] (tb::Node* const& currentNode, void* const& data = nullptr) {
                    std::cout << '\n';
                    std::cout << "Im an action in parallel node!" << "\n";
                    return tb::Node::status::success;
                  })
                  .failer()
                    .action([] (tb::Node* const& currentNode, void* const& data = nullptr) {
                      std::cout << "Im another action in parallel node!" << "\n";
                      return tb::Node::status::success;
                    })
                  .condition([] (tb::Node* const& currentNode, void* const& data = nullptr) {
                    std::cout << "Some condition before action!" << "\n";
                    return true;
                  })
                    .action([] (tb::Node* const& currentNode, void* const& data = nullptr) {
                      std::cout << "Im an action in the condition!" << "\n";
                      return tb::Node::status::running;
                    })
                  .limiter(5)
                    .action([] (tb::Node* const& currentNode, void* const& data = nullptr) {
                      std::cout << "Im an action that succeds or run only 5 times" << "\n";
                      return tb::Node::status::success;
                    })
                  // ... and others type of nodes, you can read about them below
                 .end() // parallel needs to be end
              .build();
  tree.print("  "); // for debugging

  tb::Node* running = nullptr; // for optimisation purpose you can take currently running action node
  for (size_t i = 0; i < 6; ++i) {
    tree->update(
      nullptr, // user data
      &running
    ); // use
  }

  if (running != nullptr) running->update();

  // ...
  delete tree; // dont forget to delete tree, it deletes all nodes in that tree too
}

You can even make your own node:

class Idle : public tb::Leaf {
public:
  Idle(bool* enemyInSight) : Leaf(), enemyInSight(enemyInSight) {}

  // main function
  tb::Node::status update(void* const& data = nullptr, tb::Node** runningPtr = nullptr) override {
    (void)runningPtr; // use it when you need a pointer to running node

    if (*enemyInSight) return Node::Status::Success;
    std::cout << "Im waiting!" << "\n";
    return Node::Status::Failure;
  }

  // std::string toString() const {} - can be overriden
  // void print(const std::string &indent) const {} - can be overriden
private:
  bool* enemyInSight;
};

and use it in your code:

// ...
Idle* idle = nullptr; // here would be pointer to your node
// ...
tree = builder
            // ...
            .leaf<Idle>(&idle, &someBoolPointer)
            // ...
          .build();

tree.update();
// ...
delete tree; // not needs to delete idle

All information about making your own nodes is in paragraph Inheritance.

Tree nodes description

BehaviorTreeBuilder class methods:

Compositor nodes:

sequence() - updates all nodes until faces Status::Failure or Status::Running
selector() - updates all nodes until faces Status::Success or Status::Running
parallel(minSuccess, minFail) - updates all nodes, return Status::Success if totalSuccess >= minSuccess or Status::Failure if totalFails >= minFail else Status::Running
memSelector() - updates all nodes until faces Status::Success or Status::Running, next updation will start from that node
memSequence() - updates all nodes until faces Status::Failure or Status::Running, next updation will start from that node
random(seed) - updates random (depends on seed) node
whiledo(predicate) - updates all nodes while condition == true, returns Status::Running in this case, if condition == false does nothing and returns Status::Failure

Binary nodes:

(Status::Success is true, Status::Failure is false)
conjunction() - Status first && Status second
disjunction() - Status first || Status second
equality() - Status first == Status second
implication() - Status first -> Status second (!(Status first) || Status second) (Status::Running = Status::Failure)
ifelse(predicate) - if contidion == true updates first, else second

Decorator nodes:

inverter() - inverts Status::Success and Status::Failure, Status::Running ignored
repeater(limit) - returns Status::Running limit times, then return node status and resets
limiter(limit) - returns decorator's child status limit times, then return Status::Failure
untilFail() - returns only Status::Running or Status::Failure
untilSuccess() - returns only Status::Running or Status::Success
failer() - returns only Status::Failure
succeeder() - returns only Status::Success
condition(predicate) - updates child if predicate == true else returns Status::Failure

Special nodes:

action(action) - userdefined action function, must return statuses

Other methods:

end() - use it after every Compositor or Binary nodes
add(node) - adds nodes to the current tree, NOTE: adding trees is under construction
leaf(arguments) - create userdefined action node build() - does some postworks and returns BehaviorTree pointer
debug(string) - just prints the string
setDebugCallback(callback) - set callback function for the error strings

Inheritance

Every nodes in TinyBehavior has the virtual destructor, that means technicaly you can inherit every class, but if you want make a new node type (besides Compositor, Decorator, Binary, Action and Tree) you must implement a static method constexpr static uint32_t getType() {} for the template method is() in Node class and override add() or add a new method to the BehaviorTreeBuilder (which has the virtual destructor too).
About the main types:

Compositor - methods:

addChild(node) - adds the child to the node vector
hasChildren() - is the node vector empty?
getIndex() - get the current index of node

Compositor - members:

children - nodes vector
index - current index

Decorator - methods:

setChild() - sets child
hasChild() - child != nullptr

Decorator - members:

child - decorator child

Binary - methods:

setFirstChild(node) - sets first child
setSecondChild(node) - sets second child
setChild(index, node) - sets child using index (0 or 1)
hasFirstChild() - is first child != nullptr
hasSecondChild() - is second child != nullptr

Binary - members:

nodes - pair of childs

update(data, runningNodePtr), toString() and print(indent) can be overriden from any class. Check source code for more information

Examples

Perfectly hided in example folder, provided with Makefile

ToDo

  1. Userdefined decorator/compositor/binary node create method
  2. Delete repeater?
  3. Tests
  4. Benchmarks
  5. Multithreading support
  6. Multithreading examples

Licence

MIT