Behave is a system, the purpose of which is to streamline the iterative process of designing, integrating and debugging behavioural AI. This is made possible via an implementation of behaviour trees as well as utility and blackboard methods.
Most behaviour tree implementation, though generally working under the same principles, differ slightly in their definition of the practical method of behaviour trees. Behave uses a definition very close to the one described on AIgameDev.com. So if you are completely new to behaviour trees then that site is a great source of learning.
The structure of a tree is defined by a number of interconnected control nodes, ending in leaf nodes – actions. Actions are the interfaces to your agent code, talking to both sensors (enquiring about agent or world state) and motors (affecting a change in agent or world state).
A tick of a tree flows top to bottom, evaluating branching based on tree state and then bottom to top, propagating result values and updating the tree state.
Each control node has its own rule-set setting it apart – defining which child node it ticks and how it responds to the result of it – Success/Failure or Running. In general, though, a result of Running (indicating more processing needed) will always make sure that the child in question gets evaluated again next tick.
All nodes hold a tiny bit of state to indicate the current execution flow – state such as the index of the last ticked child for sequences and selectors. This state is updated in accordance with the rule-set of the node in question – in response to the result of the current tick.
With a few exceptions, once a node has updated its state, it also completes – returning a result of Sucess/Failure or Running to its parent node. Generally this means that any tree tick will result in at least one action node ticked.
The result of the root node is the result of the entire tree execution – Running indicating that the tree has not completed and Success/Failure indicating a complete run and its result.
Before starting tree execution from its initial state, it must first be reset. This resets all node state set in earlier ticks of the tree.
Each node has a number of design-time configuration options – some types more than others. Two options shared by all are Instant and Inverse.
Inverse simply changes result values of Success to Failure and vice-versa.
The instant flag (off by default) actually alters the execution of certain parent nodes. When enabled, this can be used to have simple sensor actions not “consume” an entire tick, as its Sequence or Selector parent node will continue to its next child immediately after its evaluation.
For more details on nodes and their configuration options, see the “Nodes” chapter.
Records in Behave provide an interface rolling blackboard and utility methods into one. Handled via collections, records have three possible types:
Your runtime or a connected tree can read the floating point value of any record, addressed by name, from a given collection instance. And in the case of the field type, either can write through the same instance.
In a sense, collections are “smart dictionaries”, indexed by record names – with read and optional write access.
The Behave system is roughly divisible in four parts:
At design-time, all Behave data goes into Behave libraries, which for runtime get compiled into an executable format. It is quite possible to use multiple libraries in each project both at edit and runtime. However commonly just one library per project is used.
Unity After creating a Behave library via the Asset/Create menu or the Create dropdown in the project window, the Behave main window will pop up. You can also access it from the inspector when a Behave library is selected or from the Window menu.
Unity pre-Behave 2.7 Aside from their asset form, Behave libraries can also live as exported .behave files. Not editable before imported back into asset form again, .behave files can be handy for transferring (full or partial) libraries or backup. Import/export can be found under the Assets/Behave menu.
Unity post-Behave 2.7 From Behave 2.7 onward, Behave libraries only exist as .behave files – just as is the case for the standalone editor.
Unity From Behave 2.6 onward, Behave 1.x assets will no longer be upgradable. For this feature, please first open the asset in an earlier Behave 2 version.
Standalone Libraries live as .behave files on disk, which can be created and loaded from the sidebar menu.
In the library settings – accessible via the library name at the top of the sidebar, you can export the current library fully or partially as well as import a .behave file into the current library – merging the two.
Visually represented by a common behaviour tree design notation, the Behave designer aims to shorten the distance between idea and implementation as much as possible.
With a library being edited, trees can be added to it via the little “+” button next to the “Trees” headline in the sidebar. Double-clicking items in the sidebar lets you rename them – Unix path separators serving as a means to group trees together visually.
By drag-drop operation from the component bar to the canvas or via the keyboard interface, nodes can be easily added to your current tree. Within the canvas, easy drag-drop or keyboard operations allow you to quickly connect, configure and organise them.
Please find an overview of the keyboard mapping for the behaviour tree editor below:
|Shift + mouse drag||Drag node only (default being drag subtree)|
|Arrow keys||Move selection around the tree|
|Alt + Arrow keys||Move selected node and subtree|
|Shift + Alt + Arrow keys||Move selected node only|
|Shift + Left/Right arrow||Move selected output connection left/right|
|Return/Enter||Show insertion cursor/insert|
|Esc||Hide insertion cursor/deselect|
|Tab||Cycle insertion cursor type|
|Cmd/Ctrl + Backspace||Delete node|
|Shift + Cmd/Ctrl + Backspace||Delete input connection|
|T||Toggle component instant flag|
|V||Toggle component invert flag|
|C||Switch Parallel component completion mode|
|I||Switch Parallel component child completion mode|
|Alt + Space||Switch to tree minimap view – click to return|
Offering alternative runtime options, Agent Blueprints are edited simply as collections of trees. Similarly, they are added via the “+” button next to the “Agent blueprints” headline in the sidebar – and named by double-click.
With an Agent Blueprint selected, toggles next to the trees of the current library let you define which trees are supported by the current blueprint. Behave does track dependencies, so blueprints supporting tree A, will also support B and C if those are referenced via Reference nodes in tree A.
One tree can easily be supported by multiple blueprints.
Unity When editing an Agent Blueprint, a toggle lets you specify whether that blueprint should inherit from MonoBehaviour – thus easing integration.
Just like with behaviour trees, records can be added to the current library via the little “+” button next to the “Records” headline in the sidebar. Renaming and organising also works in the same way – with Unix path separators serving as a means to group records together visually.
However that is just about where the similarity ends. Once you create a record, you will be prompted for the record type – Field / Curve or Graph. Each record type are edited quite differently.
The Field editor simply provides a means to specify the default value of the current field, since no other data is associated with a field – aside from its record name.
With a Curve record selected, you will be presented with a simple curve editor. Here you can add, remove and edit curve keys – either via the “+” and “-” buttons and by editing the “Index” and “Value” fields or via the shortcuts below:
|Drag-drop key||Edit key index and value|
|Double-click||Add key at specified index / value|
|Cmd/Ctrl + Backspace||Remove the selected key|
In addition to its keys, a Curve also has an associated “driver” record. While the value of the Curve record is read from the y-axis, the x-axis must be provided by a second record – referred to as its driver.
Once the curve editor is open, toggles next to the list of records lets you specify which record should drive the currently selected Curve. Once a driver has been selected, its name will appear near the x-axis of the curve editor – just as the name of the Curve appears near the y-axis.
Essentially minor computations on other records, Graphs are visualised as a right-to-left tree – with the end result output from the rightmost side. The nodes making up the graph consist of the following types:
With a node selected (via mouse click or arrow-key navigation), the component bar will list the three node types aside from the type of your selection. Clicking a type in the component bar will change the type of your selection to match.
If the new type is a gate or an operator and the old type is neither, the selection will in stead be made the first child of a new node of the new type. For example: Selection is a constant of 5, you click “Gate” and your selection is now a constant of 5 – one level down from its previous position – where a gate now resides.
If the new type is constant or record and the old type is neither, the sub-graph of the current selection is removed as its type is changed.
With any node selected, you also get the options of deleting the node or forming a new Graph. The behaviour of these are the same as in the tree editor – where the latter will create a new Graph Record from the selected sub-graph and replace it in the current Graph with a Record node referencing the new Graph.
When a Record node is selected, toggles next to the library records allows you to specify which Record is referenced here. Selecting a constant lets you edit its value via text field and clicking operators and gates changes their types.
The equality gate operator comes with an additional parameter – namely precision – given that we are operating with floating points here.
Editing of Collections happens very much the same as editing Agent Blueprints. Once created and selected, toggles next to the library Records lets you specify which of them to include in the current record.
One Record can easily be supported by multiple Collections.
Once your idea has made it from your thoughts to the canvas, the next goal of Behave becomes to wire that idea to your application code as quickly as possible.
This is achieved through a compiler. It builds your trees, blueprints, records and collections to runtime accessible classes and enums. These can then be instantiated or inherited from, wired to appropriate handlers and, by direct function call, queried and evaluated.
Since your designs end up as classes and functions, they are not just fast, but also familiar and easy to use when programming your application.
To compile your currently edited library, select the library name in the sidebar, configure for debug or release, choose the compiler to use and press the “Apply changes” button.
Alternatively, click the apply changes button to the right of the “Library” sidebar header to compile with the last chosen options.
At runtime, for each compiled Behave library class, you can request new instances of your designed behaviour trees or collections. These are accessible directly via instantiate calls on the generated class – providing an appropriate value from enumerations generated from tree and collection names.
When instantiating a behaviour tree, you need to specify an agent handling the newly instantiated tree and optionally a default collection.
Trees and agents live in a symbiotic relationship. The agent ticks the tree via a function call and the tree calls back to the agent for action handling.
Agent handler functions are either resolved by the tree, on instantiation, dynamically locating and linking agent functions via introspection or by the agent deriving from a compiled agent “blueprint” – an abstract class with virtual functions for the necessary handlers. Additionally, handlers can be forwarded to targets other than the agent or indeed share a handler between multiple actions.
Tree to agent invokes with no registered handlers go to default handlers on the agent.
Any behaviour tree nodes interacting with records, will perform their appropriate get and set calls on the provided default collection.
At runtime, records only exist as part of collections. You must therefore first instantiate a collection in order to manipulate records. While instantiating a collection, a fallback collection can optionally be provided.
Once instantiated, a collection can be queried for the value of any record – although if the record is not supported by the collection or its fallback, a default value will be returned. Similarly a collection can be asked to update the value of a specific record – with curve and graph records by default ignoring such an operation, returning a failure value in stead.
Similar to behaviour tree action handlers at runtime, record getters and setters can be forwarded and overridden.
Interactions with records not included in the collection will get forwarded to the fallback collection if specified. A fallback can either be a different instantiated collection or a custom instance – implementing a provided collection interface.
Any good rapid iteration tool needs good tools for testing and validating designs. The Behave debugger fits the bill nicely.
When built in debugging mode, Behave libraries include debugging data and functionality for all contained trees and collections. Instantiated trees and collections will, to each attached debugger, continuously send information about their current state. The debugger uses this information to visually show you the current state of the active instances.
Note that since the debugger executes over the network, you can debug any Behave build on your machine, another computer, on a phone or where-ever it is running.
For debugging to function in your runtime, you need to either update it directly at your convenience / start it with a specified frequency or in your library build settings specify that the debugger should auto-launch. See the reference for more information.
Assuming that you are running a Behave library which has been built for debug and that you are updating the debugger runtime as described in the Flow section, your runtime should show up in the debugger list of the Behave window.
If you are confident in your configuration and you are still not seeing your runtime, you might want to try hitting the refresh button a couple of times. Once you see the runtime, clicking it will connect the editor to it, list all contained instances and let you debug them.
Any attached debugger may at any point unplug tree instances and manually tick or reset them. Unplugged trees will on local tick immediately return a “running” state without evaluating or changing any tree state. This allows the developer to pause a tree and step through its execution while in a relevant environment.
Debugged trees may also unplug themselves if in their execution they happen upon a node marked with a breakpoint.
Last, but definitely not the least, overrides let debuggers augment the simulation as perceived by the executing trees. This facilitates easy testing of complex or rare scenarios or perhaps the impact of new actions with no handlers implemented.