Bevy BAE (Behavior As Entities)
BAE is an implementation of Hierarchical Task Networks (HTN) for Bevy, with a focus on composability, readability, and data-driven design.
What does Behavior as Entities mean? It means you define the AI's behavior as a regular old Bevy Bundle:
use *;
use *;
Concepts
BAE implements the HTN algorithm, aka Hierarchical Task Networks. This is a bit of a mix between behavior trees and planners like GOAP.
The heart of BAE is an entity holding a Plan:
use *;
use *;
Such entities will automatically have their Plan updated and executed for you. Of course, we need to tell BAE about what to actually do in the Plan! The simplest thing to do is to execute a system:
use *;
use *;
This plan will contain a single system, greet, which will run and immediately return a success, which will advance the plan to the next step.
Since our plan only has one step, it will be completely finished at this point and replanned.
This means that this NPC will in effect spam its greeting every frame. A simple way to reduce spam is to keep the plan in the greet operator by telling BAE it's still in progress:
use *;
use *;
We can create more interesting behaviors by cobining our operators into compound tasks. Let's take a look at the Sequence task:
use *;
use *;
A Sequence will use all valid subtasks and execute them in order. In this case, it will call greet once, then advance the plan, and finally stay forever in idle.
What does valid mean here? BAE uses bevy_mod_props, which attaches arbitrary key-value properties to entities.
BAE uses these properties to set up Conditions:
use *;
use *;
Spawning this plan will never run the greet system, as we never set the can_greet property on the entity. If a property is not set, it uses a default value, which is false in this case.
Props is a regular component on the entity, so there are multitudes of accessing and editing the properties. But Commands also has a handy method for this:
use *;
use *;
Let's take this one step further and learn about the Select compound task. Select uses the first task that is valid:
use *;
use *;
Here, Select will first try to plan the greet operator, but can't, since the can_greet property was never set. So, it falls back to the idle behavior.
The real spice in this comes from the fact that operators themselves can also change properties after they ran!
use *;
use *;
Note that while we require conditions apply effects on operators here, the same can be done with compound tasks.
Let's consider what happens when running our app.
- First,
Selectwill try to plangreet, but cannot, ascan_greetis not set totrue. - Next,
Selectwill planprepare_to_greetas a fallback - Once
prepare_to_greetran,can_greetis set totrue. - The plan ran out of operators, so it will replan
Selectwill again try to plangreet, and this time it will be able to!prepare_to_greetwill never be called again, ascan_greethas a higher priority.
Conditions and effects are also "anticipated" correctly during planning. Let's go back to Sequence for a second:
use *;
use *;
Here, Sequence will plan prepare_to_greet and knows that it will set can_greet after it runs,
so it also knows that greet will have its conditions fulfilled by the time it wants to run.
This means that Sequence can successfully include both prepare_to_greet and greet in the same plan!
And that's most there is to HTNs. The last important realization is that we can nest as many compound tasks as we want. Scroll back up this document to the initial example we gave, which defines the behavior for Trunk Thumper the troll. Try to think through how our troll will behave.
Terminology Notes
I used terminology that I felt was intuitive for a Bevy context. But if you're familiar with HTN, you may have scratched your head a bit at the explanation above. The cheatsheet for how traditional HTN terminology maps to BAE is
- Domain: the entity holding the
Plan, as well as its associated tree of relations. - Primitive Task: The entity holding the
Operatorand its optionalEffectsandConditions. - Method: Combined into the entity holding the
CompoundTask, associatedTasks, and optionalEffectsandConditions. - Compound Task: The
Tasks. This was because it's the same as having aSequenceof anOperator::noopholding a condition, followed by a compound task. - Operator: the system referenced by an
Operator.
Compatibility
| bevy | bevy_bae |
|---|---|
| 0.17 | 0.1 |