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:

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
  • Please add some pre-action features:enhancement

    Please add some pre-action features:enhancement

    I hope that this kind of feature is added.

    1. Register some function to the bonsai bt.
    fn check_sleep(..) -> (Status, f64) {
        if monster.is_slept() {
            return (Running, 0.0)
        }
        ... // do next action
    
    1. Select actions to which the function is called.
    bt.register(check_sleep, vec![Monster::Attack, Monster::Walk, ...]);
    
    1. Registered function is called before selected action is called in a behavior tree.
    opened by PudgeKim 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
A no-frills Tetris implementation written in Rust with the Piston game engine, and Rodio for music.

rustris A no-frills Tetris implementation written in Rust with the Piston game engine, and Rodio for music. (C) 2020 Ben Cantrick. This code is distri

Ben Cantrick 17 Aug 18, 2022
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 86 Dec 23, 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 16 Dec 14, 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 Nov 17, 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 8 Dec 22, 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 77 Dec 28, 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
A Rust implementation of the GMT game Battle Line.

rsbl A Rust implementation of the game Battle Line. Running Currently there is only the simplest prototype of the game: a text-based table with ⚑ char

Matan Lurey 2 Nov 6, 2022
This is an implementation of "Game of Life" by Horton Conway in Rust using raylib.

Game-of-Life This is an implementation of "Game of Life" by Horton Conway in Rust using raylib. What is Game of Life? The Game of Life, also known sim

Berkay Sahin 3 Dec 26, 2022
📺 An implementation of the retro bouncing DVD logo screensaver animation built with bevy and rust

?? Bevy bouncing DVD logo This project is a simple demonstration of using the Bevy game engine and the Rust programming language to create a bouncing

Victor Aremu 5 Jan 12, 2023
A basic raytracer implementation in Rust based on the Ray Tracing in One Weekend book.

Raytracer A basic raytracer implementation in Rust based on the Ray Tracing in One Weekend book. Live Demo Result How to Run Standalone Binary $ cargo

Navin Mohan 19 Dec 13, 2022
Rust implementation of the Minecraft authentication server (Yggdrasil)

yggoxide This crate currently implements the REST API for: Service Exposed at Minecraft Production Coverage Yggdrasil authentication / and /authserver

Mojank Studios 16 Jun 26, 2023
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 63 Nov 12, 2022
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

Florent Michel 4 Oct 3, 2022
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

Sébastien Castiel 58 Nov 23, 2022
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

Shantanu Deshmukh 6 Apr 11, 2022