12 unstable releases (3 breaking)

0.4.9 Aug 18, 2022
0.3.9 Aug 8, 2022
0.3.8 Jul 25, 2022
0.2.0 Jul 12, 2022
0.1.0 Jul 12, 2022

#171 in Game dev

Download history 4/week @ 2023-06-02 11/week @ 2023-06-09 17/week @ 2023-06-16 16/week @ 2023-06-23 35/week @ 2023-06-30 19/week @ 2023-07-07 10/week @ 2023-07-14 11/week @ 2023-07-21 4/week @ 2023-07-28 6/week @ 2023-08-04 2/week @ 2023-08-11 14/week @ 2023-08-18 26/week @ 2023-08-25 16/week @ 2023-09-01 7/week @ 2023-09-08 12/week @ 2023-09-15

63 downloads per month

MIT license

975 lines

Bonsai 盆栽

Rust implementation of Behavior Trees

Build Status Bonsai crate minimum rustc 1.60 Docs codecov Maintenance GitHub pull-requests GitHub pull-requests closed ViewCount License: MIT


Using Bonsai

Bonsai is available on crates.io. The recommended way to use it is to add a line into your Cargo.toml such as:

bonsai-bt = "*"

What is a Behavior Tree?

A Behavior Tree (BT) is a data structure in which we can set the rules of how certain behavior's can occur, and the order in which they would execute. BTs are a very efficient way of creating complex systems that are both modular and reactive. These properties are crucial in many applications, which has led to the spread of BT from computer game programming to many branches of AI and Robotics.

How to use a Behavior tree?

A Behavior Tree forms a tree structure where each node represents a process. When the process terminates, it signals Success or Failure. This can then be used by the parent node to select the next process. A signal Running is used to tell the process is not done yet.

For example, if you have a state A and a state B:

  • Move from state A to state B if A succeeds: Sequence([A, B])
  • Try A first and then try B if A fails: Select([A, B])
  • If condition succeedes do A, else do B : If(condition, A, B)
  • If A succeeds, return failure (and vice-versa): Invert(A)
  • Do B repeatedly while A runs: While(A, [B])
  • Do A, B forever: While(WaitForever, [A, B])
  • Run A and B in parallell and wait for both to succeed: WhenAll([A, B])
  • Run A and B in parallell and wait for any to succeed: WhenAny([A, B])
  • Run A and B in parallell, but A has to succeed before B: After([A, B])

See the Behavior enum for more information.

Example of use

This is a enemy NPC (non-player-character) behavior mock-up which decides if the AI should shoot while running for nearby cover, rush in to attack the player up close or stand its ground while firing at the player.

Tree vizualization


use std::{collections::HashMap, thread::sleep, time::Duration};

use bonsai_bt::{
    Behavior::{Action, Select, Sequence},
    Event, Status, Running, Timer, UpdateArgs, BT,

type Damage = u32;
type Distance = f64;

#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq)]
enum EnemyNPC {
    /// Run
    /// Cover
    /// Blind fire
    /// When player is close -> melee attack
    /// distance [m], damage
    MeleeAttack(Distance, Damage),
    /// When player is far away -> fire weapon

use game::{Enemy, Player}; // fictive game imports

fn game_tick(timer: &mut Timer, bt: &mut BT<EnemyNPC, String, serde_json::Value>) {
    // how much time should the bt advance into the future
    let dt = timer.get_dt();

    // proceed to next iteration in event loop
    let e: Event = UpdateArgs { dt }.into();

    bt.state.tick(&e,&mut |args: bonsai_bt::ActionArgs<Event, EnemyNPC>| {
        match *args.action {
            EnemyNPC::Run => {
              Enemy::run_away_from_player(); // you must implement these methods
              (bonsai_bt::Running, 0.0)
            EnemyNPC::GetInCover => {
              let in_cover: Bool = Enemy::get_in_cover();
              if in_cover {
                (bonsai_bt::Success, dt)
              } else {
                (bonsai_bt::Running, 0.0)
            EnemyNPC::BlindFire(damage) => {
              let has_ammo: Bool = Enemy::has_ammo();
              if has_ammo {
                (bonsai_bt::Success, dt)
              } else {
                (bonsai_bt::Failure, dt)
            EnemyNPC::MeleeAttack(dist, damage) => {
              let player = Player::get_player();
              let pos = Enemy::get_pos();
              let diff = sub(*pos, player.pos);
              if len(diff) < dist {
                  let &mut player_health = Player::get_health();
                  *player_health = Player::decrease_health(damage);
                  (bonsai_bt::Success, dt)
              } else {
                  (bonsai_bt::Failure, dt)
            EnemyNPC::FireWeapon(damage) => {
              let has_ammo: Bool = Enemy::has_ammo();
              if has_ammo {
                (bonsai_bt::Success, dt)
              } else {
                (bonsai_bt::Failure, dt)

fn main() {
    use crate::EnemyNPC::{BlindFire, FireWeapon, GetInCover, MeleeAttack, Run};

    // define blackboard (even though we're not using it)
    let blackboard: HashMap<String, serde_json::Value> = HashMap::new();

    // create ai behavior
    let run = Action(Run);
    let cover = Action(GetInCover);
    let run_for_five_secs = While(Box::new(Wait(5.0)), vec![run]);
    let run_and_shoot = While(Box::new(run_for_five_secs), vec![Action(BlindFire(50))]);
    let run_cover = Sequence(vec![run_and_shoot, cover]);

    let player_close = Select(vec![Action(MeleeAttack(1.0, 100)), Action(FireWeapon(50))]);
    let under_attack_behavior = Select(vec![run_cover, player_close]);

    let bt_serialized = serde_json::to_string_pretty(&under_attack_behavior).unwrap();
    println!("creating bt: \n {} \n", bt_serialized);
    let mut bt = BT::new(under_attack_behavior, blackboard);

    // create a monotonic timer
    let mut timer = Timer::init_time();

    loop {
        // decide bt frequency by sleeping the loop
        sleep(Duration::new(0, 0.1e+9 as u32));

        // tick the bt
        game_tick(&mut timer, &mut bt);

Compile the behavior tree into a graphviz compatible DiGraph.

let mut bt = BT::new(under_attack_behavior, blackboard);

// produce a string DiGraph compatible with graphviz
// paste the contents in graphviz, e.g: https://dreampuf.github.io/GraphvizOnline/#
let g = bt.get_graphviz();
println!("{}", g);

Similar Crates

Bonsai is inspired by many other crates out there, here's a few worth mentioning:


~63K SLoC