bevy_behave
A behaviour tree plugin for bevy with dynamic spawning.
When an action (leaf node / task node) in the behaviour tree runs, it will spawn an entity with the components you specified in the tree definition. The tree then waits for this entity to trigger a status report, at which point the entity will be despawned.
Conditionals are implemented with observers, see below.
This was an experiment to see if I could make an ergonomic bevyish way to do behaviour trees, I think it's turning out fairly well. Please do offer feedback (good or bad) if you check it out!
let npc_entity = get_enemy_entity;
let player_entity = get_player_entity;
// the tree definition (which is cloneable).
// and in theory, able to be loaded from an asset file (unimplemented).
// when added to the BehaveTree component, this gets transformed internally to hold state etc.
let tree = tree! ;
// Spawn an entity to run the behaviour tree.
// Make it a child of the npc entity for convenience.
// The default is to assume the Parent of the tree entity is the Target Entity you're controlling.
commands.spawn.set_parent;
When a dynamic spawn happens, the entity is given the components you provided along with a
BehaveCtx component, which will tell you the target entity the tree is controlling, and a
mechanism to trigger a status report for success or failure.
Have a look at the chase example.
Control Flow Nodes
Currently supported control flow nodes:
| Node | Description |
|---|---|
| Sequence | Runs children in sequence, failing if any fails, succeeding if all succeed |
| Fallback | Runs children in sequence until one succeeds. If all fail, this fails |
| Invert | Inverts success/failure of child. Must only have one child |
| AlwaysSucceed | Always succeeds |
| AlwaysFail | Always fails |
| TriggerReq | Triggers an event, which the user observes and responds to with a success or failure report |
Task Nodes
| Node | Description |
|---|---|
| Wait | Waits this many seconds before SucceedingTimer is ticked inside the tree, no entities are spawned. |
| DynamicSpawn | Spawns an entity when this node in the tree is reached, and waits for it to trigger a status report.Once the entity triggers a status report, it is immediately despawned. |
Unimplemented but possibly useful Task Nodes:
| Node | Description |
|---|---|
| ExistingEntity | When this node on the tree is reached, a BehaveCtx is inserted.The tree then waits for this entity to trigger a status report.On completion, BehaveCtx is removed, but nothing is despawned. |
How conditionals/non-spawning tasks work
I'm using observer events to implement no-entity-required tasks. You specify an arbitrary struct which is
delivered in a generic trigger which also carries a BehaveTriggerCtx value.
The observer can then respond with success or failure.
// Conditionals are types that are delivered by a trigger:
// add a global observer to answer conditional queries for HeightCheck:
app.add_observer;
// you respond by triggering a success or failure event created by the ctx:
Performance
- There's just one global observer for receiving task status reports from entities or triggers.
- Most of the time, the work is being done in a spawned entity using one of your action components, and in this state, there is a marker on the tree component so it doesn't tick or do anything until a result is ready.
- Avoided mut World systems – the tree ticking should be able to run in parallel with other things (i think).
- So a fairly minimal wrapper around basic bevy systems.
In release mode i can happily toss 10k enemies in the chase demo and zoom around at max framerate. It gets slow rendering a zillion gizmo circles before any bevy_behave stuff gets in the way.
https://github.com/user-attachments/assets/e12bc4dd-d7fb-4eca-8810-90d65300776d
License
Same as bevy: MIT or Apache-2.0.
Notes
Alternative approach for conditionals
I considered doing control flow by taking an IntoSystem with a defined In and Out type,
something like this:
pub type BoxedConditionSystem = ;
Then you could defined a cond system like, which is quite convenient:
However I don't think the resulting data struct would be cloneable, nor could you really read it from an asset file for manipulation (or can you?)
I would also need mutable World in the "tick trees" system, which would stop it running in parallel maybe. Anyway observers seem to work pretty well.