Rust implementation of behavior trees.

Overview

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

Contents

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:

[dependencies]
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

Implementation

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
    Run,
    /// Cover
    GetInCover,
    /// Blind fire
    BlindFire(Damage),
    /// When player is close -> melee attack
    ///
    /// distance [m], damage
    MeleeAttack(Distance, Damage),
    /// When player is far away -> fire weapon
    FireWeapon(Damage),
}

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

    #[rustfmt::skip]
    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 {
                Enemy::shoot_in_direction();
                (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 {
                Enemy::shoot_at_player();
                (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);
    }
}

Similar Crates

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

You might also like...
A simple implementation of Conway's Game of Life using Fully homomorphic Encryption

Game of life using Fully homomorphic encryption A simple implementation of Conway's Game of Life built using Zama's concrete-boolean library. Build Ju

An implementation of the Game of Life

Lifeee – An implementation of the Game of Life I realized this application to keep learning Rust, discover the front-end library Yew, and because I’m

A wordle implementation that I made for a competition of wordle solvers

Wordle tester A wordle implementation that I made for a competition of wordle solvers. Runs tests using a list of words and outputs the total turns ta

The study of a simple path tracer implementation (image raytracing in shorts)
The study of a simple path tracer implementation (image raytracing in shorts)

The study of a simple path tracer implementation (generate a raytraced image, in shorts).

RusTTS is an unofficial Coqui TTS implementation.

RusTTS RusTTS is an unofficial Coqui TTS implementation. Currently, only the YourTTS for [ TTS & VC ] has been implemented. So, feel free to contribut

bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy
bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy

Bevy Voxel Cone Tracing bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy. Bevy Version

An alternative ggez implementation on top of miniquad.
An alternative ggez implementation on top of miniquad.

Good Web Game good-web-game is a wasm32-unknown-unknown implementation of a ggez subset on top of miniquad. Originally built to run Zemeroth on the we

An implementation of the Jump Flooding Algorithm for the Bevy engine.

bevy_jfa The Jump Flooding Algorithm (JFA) for Bevy. Features This crate provides an OutlinePlugin that can be used to add outlines to Bevy meshes. Se

Rust-raytracer - 🔭 A simple ray tracer in Rust 🦀
Rust-raytracer - 🔭 A simple ray tracer in Rust 🦀

rust-raytracer An implementation of a very simple raytracer based on Ray Tracing in One Weekend by Peter Shirley in Rust. I used this project to learn

Comments
  • Find a node editor that can take the behavior tree and convert into a tree/graph

    Find a node editor that can take the behavior tree and convert into a tree/graph

    egui_node_graph looks like the best option.

    Potential flow:

    1. Create behavior tree
    2. Convert behavior tree to a immutable node grapha
    3. Could use mpsc channels to tell which node is active

    We could also have that the behavior tree sets up a websocket service, that writes which node is active. Then a second service will read that and compile the tree. The second service would need to read a json to know how the tree is composed such that it can be visualized.

    opened by Sollimann 3
  • How do I change wait seconds dynamically?

    How do I change wait seconds dynamically?

    When I'm using like Wait(1.1), it will wait for 1.1 seconds. I want to change that value without re-constructing whole tree. Or can Action wait for specific period? I've tried return (Status, f64) with custom f64 value, but it doesn't seem to work.

    help wanted 
    opened by snskshn 2
  • feat/tree-visualization

    feat/tree-visualization

    Steps:

    This PR:

    1. Parse tree as PetGraph
    2. Convert Petgraph to dot files

    Next PR: 4. Convert PetGraph to node diagram 5. Track which Action is active and use Petgraph depth-first-traversal (reversed) to find active tree traversal

    opened by Sollimann 0
  • Add node editor for real-time visualization of behavior tree

    Add node editor for real-time visualization of behavior tree

    egui_node_graph looks like the best option.

    Potential flow:

    1. Create behavior tree
    2. Convert behavior tree to a immutable node graph
    3. Could use mpsc channels to tell which node is active

    We could also have that the behavior tree sets up a websocket service, that writes which node is active. Then a second service will read that and compile the tree. The second service would need to read a json to know how the tree is composed such that it can be visualized.

    opened by Sollimann 0
Releases(v0.4.9)
Owner
Kristoffer Rakstad Solberg
Software Engineer II - Robotics Team @cognitedata. M.Sc in ICT & Marine Cybernetics from NTNU and NUS Singapore
Kristoffer Rakstad Solberg
An (unofficial) open source Rust implementation of the Discord Game SDK.

⚔️ discord-sdk An (unofficial) open source Rust implementation of the Discord Game SDK. Why not use this? This project is not official and is using a

Embark 70 Sep 20, 2022
Rust implementation of Another World (aka Out of this world) game engine

RustyWorld Rust implementation of Another World (aka Out of this world) game engine. I wanted a fun project to challenge myself while learning Rust, a

Francesco Degrassi 3 Jul 1, 2021
Implementation of the great book Ray Tracing in One Weekend in Rust.

Ray Tracing in One Weekend (Rust) Implementation of the great book Ray Tracing in One Weekend in Rust. Fun easy way to pick up and learn Rust (was rou

Stanley Su 6 Dec 29, 2021
A Rust implementation of the legendary solitaire game

Freecell Yet another implementation of the legendary total information solitaire. Play patience like it's 1991, complete with sights and sounds. Build

null 15 Aug 25, 2022
Game of life implementation written in Rust.

Game of life Game of life implementation written in Rust. Part of my journey in learning Rust. Pattern files The patterns are based on the example pat

Hashem Hashem 2 Jan 3, 2022
A simple, very minimal Minecraft server implementation in Rust.

A simple, very minimal Minecraft server implementation in Rust. For a simple Minecraft server that isn't supposed to do much (for example, a limbo ser

Chris 7 Aug 17, 2022
Rust implementation of the Nomic Bitcoin sidechain

Nomic Bitcoin Bridge testnet v0.3.0 (codename "gucci") Guccinet In this testnet, we've added two core featues: staking and Bitcoin integration. Full s

Nomic 63 Sep 5, 2022
A first-time implementation of Conway's Game of Life in Rust: Adventure and Commentary

A Commentary on Life This project documents the process and final result of my first-ever attempt at implementing Conway's Game of Life. I'll be using

Avery R. 2 Feb 25, 2022
A Quoridor implementation in Rust, including different AIs

Quoridor in Rust Quoridor is a zero-sum strategic board game with complete information. It was initially designed by Marko Marchesi based on Blockade

canta slaus 2 Sep 8, 2022
Terminal UI implementation and types for the Dark Forest game

dark-forest.rs Terminal UI implementation and types for the Dark Forest game Development We use the standard Rust toolchain cargo check

Georgios Konstantopoulos 60 Aug 31, 2022