Bonsai: Robust, flexible and fast Behaviour Trees with a visual, intuitive, node editor (WIP)

The project has been in development for about 3 months and I wish to show the community the current progress. Quick video showing usage of the editor.

What is a behaviour tree?
Behaviour Trees are a common tool used to control the flow of logic to execute tasks and are most prominently used to create complex game A.I. but can be extended for any use you see fit. The essence of behaviour trees is to enhance functionality. You can go as far as having a state machine that switches between trees at will or a tree that contains a state machine as a task.

Goals of the project

  • Lightweight, robust, and fast behaviour trees.
  • Visual editor to improve the workflow when creating, running, and testing behaviour trees.
  • Seamless integration with the Unity workflow/environment.

Features Overview:

  • The core behaviour tree engine with a set of standard composite, decorator, and task nodes.
  • Blackboard to share data between tasks.
  • A visual editor to create, edit, view, and debug trees
  • Conditional Aborts (AKA Observer aborts)
  • Includes: Parallel execution, Interrupts, Semaphore Guards, Reactive (Dynamic) Selectors.
  • Supports including Sub-trees
  • Can easily create custom Composites, Decorators, Tasks
  • Behaviour trees are ScriptableObjects, so it integrates perfectly with the Unity Editor.

Behaviour tree running.

Run-time Editor Features and Limitations
During run-time you can view how the a tree executes and see which nodes are running, the statuses returned (success/failure) or if the nodes were aborted/interrupted.

You can also edit certain properties of a node, like changing the abort type, or setting a new waiting time for the wait task via the Unity Inspector.

Things that cannot be currently edited in run-time are (this may change in the future):

  • Adding/deleting nodes
  • Changing the root
  • Changing connections between nodes

Editor Features

  • A canvas which can be panned and zoomed
  • Add, delete, drag, duplicate, and connect nodes.
  • There is multi selection support so you can apply multi-edit/drag/duplicate/delete.
  • Grid snapping
  • Sub-tree dragging - when you drag a node, the entire sub-tree under it drags along.
  • Save and load behaviour tree assets.
  • Nodes resize properly to fit internal contents such as the name of node.
  • Context menus to organize nodes.
  • Attributes which can be used on a custom node class to categorize and add an icon to your custom node.
  • A custom blackboard inspector to add variables (specify key and type).
  • Custom inspector for nodes that need to reference other nodes like Interrupts and Semaphore Guards, the inspector lets you push a button to activate a linking action, in which you can click on nodes to link.
  • A simple “nicify” feature which automatically lays out the tree neatly.
  • Visual highlighting feedback to quickly see what nodes are being referenced by other nodes and which nodes get aborted by a conditional abort.
  • Multiple behaviour tree editors can be opened at once.
  • Viewing a running behaviour tree just requires clicking on a game object with behaviour tree component.
  • Behaviour tree assets can be opened by double clicking on the asset file.
  • The UNDO is still not implemented.

API and Custom Tasks
There are four main categories of nodes which you can extend from to add functionality:

  • Composite
  • Decorator
  • Conditional Abort
  • Conditional Task
  • Task

In order to add custom functionality you can override key methods:

// Called only once when the tree is started.
public virtual void OnStart() { }

// The logic that the node executes.
public abstract Status Run();

// Called when a traversal begins on the node.
public virtual void OnEnter() { }

// Called when a traversal on the node ends.
public virtual void OnExit() { }

// The priority value of the node.
// Default value for all nodes is the negated pre-order index,
// since lower preorders are executed first (default behaviour).
public virtual float Priority() { }

// Called when a child caused an abort.
protected internal virtual void OnAbort(ConditionalAbort aborter) { }

// Call when a child finished executing
protected internal virtual void OnChildExit(int childIndex, Status childStatus) { }

// Called once after the entire tree is finished being copied.
// Should be used to setup special BehaviourNode references.
public virtual void OnCopy() { }

Example of a simple, custom Wait task:

[NodeEditorProperties("Tasks/", "Timer")]
public class Wait : Task
{
    private float _timer = 0f;

    public float waitTime = 1f;

    public override void OnEnter()
    {
        _timer = 0f;
    }

    public override Status Run()
    {
        _timer += Time.deltaTime;

        if (_timer >= waitTime) {
            return Status.Success;
        }

        return Status.Running;
    }
}

The trickier nodes to extend are composite nodes since they require knowing how to manipulate the “Iterator” in order to traverse nodes. The iterator can be manipulated to dictate how to traverse the tree.

Performance

This is a benchmark running 5000 trees. No GC after startup. The tree in the image is the tree used for benchmark. Tested on a Intel Core i7-4790 @ 4 GHz. (Windows)

I have also ran the same benchmark on a Linux laptop. Intel i5-6200U @ 2.30 GHz. The “Time ms” was on average 4 ms.

Limitations
Since I was aiming for a lightweight system, I decided not to provide complete, built-in functionality for serialization (data persistence is not available between running game build sessions). The tree and blackboard structure is saved as an asset, but changing data values will not be persistent between game runs. For example, if you have a blackboard variable [“timer”, 0.0f] and during the game run, the value goes up to say 10.0, you would need to save and load that value manually so its persistent between game saves.

I might add a simple system to serialize basic variable types in the blackboard such as int, string, Vector, structs…etc, the difficult, tricky part would be saving persistent object references or very complex objects like dictionaries with objects.

Upcoming Features

  • Undo functionality. Any modification to the tree will be undo-able.

  • Run-time tree editing. This will allow you to (when the game/tree is running):

  • Add and delete nodes

  • Add children and re-parent nodes

  • Change connections between nodes

  • Change the child execution order

  • Change the root

  • Change the type of node, for example switching a Sequence to a Selector. The more complex version for this would changing a node to a Parallel node.

  • Change node references (changing linked interrupts or guards)

  • Plus more

  • Extendable friendly editor. I want plugin creation to be simple to implement. There would be an Editor API that allows you to add custom behavior to the editor or allow you to completely change the look of the editor.

Screenshots
The IsKeyDown decorator has a lower priority abort type set, so the sub-trees to the right are highlighted since they can be aborted by the decorator.

Here the semaphore guards are linked which highlight in orange, you can also see the custom inspector for the guard, making it easy to link other guards together.

[Additional screenshots and videos of Bonsai]
Check out the link to view more screen shots and actual footage using the editor.


3093320--233372--Bonsai Logo and Title Medium.png


3 Likes

I am thinking of going forward with full editing capabilities during run-time. The functionality is basically there… but I have to define some rules determining what should happen if a node is deleted but the current running node was a child, and make sure that the tree structure and the traversal stack are synchronized with new modifications.

Run-time edits will provide the user with more freedom to explore and observe immediate changes while the tree is running.

Hopefully I can transfer all editing functionality to run-time which include:

  • Add and delete nodes
  • Add children and re-parent nodes
  • Change connections between nodes
  • Change the child execution order
  • Change the root
  • Change the type of node, for example switching a Sequence to a Selector. The more complex version for this would changing a node to a Parallel node.
  • Plus more

I am going to try out and use the Command Pattern to implement the Undo. It is going to take some work but it would be efficient memory and speed wise. This means I am going to have to re-implement the Input system with the new Command Pattern architecture.

A tough challenge is handling Undo during run-time. The easiest thing is to disable Undo during run-time.
Undo for run-time would require handling:

  • Current active iteration. The most obvious thing to do is to store the iterator traversal state before any change to the tree and the undo/redo uses the stored state.
  • Synchronizing cached references. Certain entities are cached for performance such as Conditional Aborts, Parallel nodes, interruptables and guards.
  • Synchronizing parallel node’s sub-iterators. Whenever a parallel node changes the number of children, then the sub-iterators under that parallel node need to compensate for the change.
  • Updating the internal array structure of the tree in pre-order whenever a change happens.

Well I did get Undo working but it is in another project. I might need to adapt this framework with the new one: GitHub - luis-l/UnityNodeEditorBase: Basic editor extension functionality to get a node editor up and running.

Where is this asset? Because looks sooooo cooool

It has been forever, but I made this open source GitHub - luis-l/UnityAssets: Some Unity assets I worked on

1 Like