303 lines
7.0 KiB
Markdown
303 lines
7.0 KiB
Markdown
|
|
# Making Characters Act
|
||
|
|
|
||
|
|
In the previous chapter, you created behavior trees with selectors and sequences. Now you will add conditions, action parameters, and decorators to create dynamic, responsive behaviors.
|
||
|
|
|
||
|
|
## Conditions: if and when
|
||
|
|
|
||
|
|
Conditions let behavior trees react to the world. Use `if` or `when` to test a condition before proceeding:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior Martha_React {
|
||
|
|
choose response {
|
||
|
|
then bake_path {
|
||
|
|
if(inventory_sufficient)
|
||
|
|
StartBaking
|
||
|
|
}
|
||
|
|
|
||
|
|
then restock_path {
|
||
|
|
if(inventory_low)
|
||
|
|
OrderSupplies
|
||
|
|
}
|
||
|
|
|
||
|
|
CleanWorkstation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`if(inventory_sufficient)` succeeds when inventory is sufficient, and fails otherwise. If it fails, the entire `bake_path` sequence fails, and the tree moves on to the next option.
|
||
|
|
|
||
|
|
`if` and `when` are interchangeable -- use whichever reads more naturally:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// "if" for state checks
|
||
|
|
if(health < 20)
|
||
|
|
|
||
|
|
// "when" for event-like conditions
|
||
|
|
when(alarm_triggered)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Condition Expressions
|
||
|
|
|
||
|
|
Conditions support comparisons and logical operators:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Comparisons
|
||
|
|
if(health < 20)
|
||
|
|
if(distance > 100)
|
||
|
|
if(name == "Martha")
|
||
|
|
if(status is Curious) // 'is' is syntactic sugar for ==
|
||
|
|
|
||
|
|
// Logical operators
|
||
|
|
if(hungry and tired)
|
||
|
|
if(rich or lucky)
|
||
|
|
if(not is_dangerous)
|
||
|
|
|
||
|
|
// Combined
|
||
|
|
if(health < 50 and not has_potion)
|
||
|
|
if((age > 18 and age < 65) or is_veteran)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Action Parameters
|
||
|
|
|
||
|
|
Actions can take named parameters using parenthesis syntax:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior Martha_BakeSpecial {
|
||
|
|
then baking {
|
||
|
|
MixDough(recipe: "sourdough", quantity: 10)
|
||
|
|
KneadDough(duration: 15m)
|
||
|
|
BakeLoaves(temperature: 230, duration: 35m)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Parameters are fields inside `( )` after the action name. They let you customize behavior without defining separate actions for each variation.
|
||
|
|
|
||
|
|
## Decorators
|
||
|
|
|
||
|
|
Decorators wrap a single child node and modify its behavior. They are your tools for timing, repetition, and conditional execution.
|
||
|
|
|
||
|
|
### repeat -- Looping
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Infinite repeat (checks oven forever)
|
||
|
|
repeat {
|
||
|
|
CheckOvenTemperature
|
||
|
|
}
|
||
|
|
|
||
|
|
// Repeat exactly 3 times
|
||
|
|
repeat(3) {
|
||
|
|
KneadDough
|
||
|
|
}
|
||
|
|
|
||
|
|
// Repeat between 2 and 5 times (random)
|
||
|
|
repeat(2..5) {
|
||
|
|
FoldDough
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### invert -- Flip Results
|
||
|
|
|
||
|
|
Inverts success/failure. Useful for "if NOT" conditions:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior SafeBake {
|
||
|
|
choose options {
|
||
|
|
then bake_safely {
|
||
|
|
invert { OvenOverheating } // Succeeds if oven is NOT overheating
|
||
|
|
ContinueBaking
|
||
|
|
}
|
||
|
|
|
||
|
|
StopAndInspect
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### retry -- Try Again on Failure
|
||
|
|
|
||
|
|
Retries the child up to N times if it fails:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
retry(3) {
|
||
|
|
LightOven // Try up to 3 times before giving up
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### timeout -- Time Limits
|
||
|
|
|
||
|
|
Fails the child if it does not complete within the duration:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
timeout(10s) {
|
||
|
|
WaitForDoughToRise // Must finish within 10 seconds
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### cooldown -- Rate Limiting
|
||
|
|
|
||
|
|
Prevents the child from running again within the cooldown period:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
cooldown(30s) {
|
||
|
|
CheckOvenTemperature // Can only check once every 30 seconds
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### if as Decorator (Guard)
|
||
|
|
|
||
|
|
The `if` decorator only runs the child when a condition is true:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
if(has_special_orders) {
|
||
|
|
PrepareSpecialBatch // Only prepare when there are orders
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This is different from `if` as a condition node. As a decorator, `if` wraps a child and gates its execution. As a condition node, `if` is a simple pass/fail check inline in a sequence.
|
||
|
|
|
||
|
|
### succeed_always and fail_always
|
||
|
|
|
||
|
|
Force a result regardless of the child:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Try bonus task, but don't fail the routine if it fails
|
||
|
|
succeed_always {
|
||
|
|
ExperimentWithNewRecipe
|
||
|
|
}
|
||
|
|
|
||
|
|
// Temporarily disable a feature
|
||
|
|
fail_always {
|
||
|
|
UntestedBakingMethod
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Combining Decorators
|
||
|
|
|
||
|
|
Decorators can nest for complex control:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior ResilientAction {
|
||
|
|
// Only run if oven is ready, with 20s timeout, retrying up to 3 times
|
||
|
|
if(oven_ready) {
|
||
|
|
timeout(20s) {
|
||
|
|
retry(3) {
|
||
|
|
BakeDelicateItem
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Execution flows outside-in: first the `if` checks the oven, then the timeout starts, then the retry begins.
|
||
|
|
|
||
|
|
## Subtree References
|
||
|
|
|
||
|
|
The `include` keyword references another behavior tree, enabling reuse:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior SourdoughRecipe {
|
||
|
|
then sourdough {
|
||
|
|
MixDough(recipe: "sourdough", quantity: 10)
|
||
|
|
KneadDough(duration: 15m)
|
||
|
|
FirstRise(duration: 2h)
|
||
|
|
ShapeLoaves
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
behavior Martha_DailyRoutine {
|
||
|
|
choose daily_priority {
|
||
|
|
then special_orders {
|
||
|
|
if(has_special_orders)
|
||
|
|
include SpecialOrderBehavior
|
||
|
|
}
|
||
|
|
|
||
|
|
include SourdoughRecipe // Reuse sourdough behavior
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Subtrees help you avoid duplicating behavior logic. You can also reference behaviors from other modules using qualified paths:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
include behaviors::baking::sourdough
|
||
|
|
include behaviors::service::greet_customer
|
||
|
|
```
|
||
|
|
|
||
|
|
## Behavior Linking with Priorities
|
||
|
|
|
||
|
|
Characters can link to multiple behaviors with priorities and conditions:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
character Martha: Human {
|
||
|
|
uses behaviors: [
|
||
|
|
{
|
||
|
|
tree: BakerRoutine
|
||
|
|
priority: normal
|
||
|
|
},
|
||
|
|
{
|
||
|
|
tree: HandleEmergency
|
||
|
|
when: emergency_detected
|
||
|
|
priority: high
|
||
|
|
},
|
||
|
|
{
|
||
|
|
tree: HandleHealthInspection
|
||
|
|
when: inspector_present
|
||
|
|
priority: critical
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The runtime evaluates behaviors by priority (critical > high > normal > low). Higher-priority behaviors preempt lower-priority ones when their conditions are met.
|
||
|
|
|
||
|
|
## A Complete Example
|
||
|
|
|
||
|
|
Here is a complete behavior tree for handling the morning rush:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior MorningRush_Routine {
|
||
|
|
---description
|
||
|
|
The bakery's morning rush routine: serve customers, restock,
|
||
|
|
and keep the ovens running.
|
||
|
|
---
|
||
|
|
|
||
|
|
repeat {
|
||
|
|
then rush_cycle {
|
||
|
|
// Serve any waiting customers
|
||
|
|
choose service_mode {
|
||
|
|
then serve_regular {
|
||
|
|
if(customer_waiting)
|
||
|
|
GreetCustomer
|
||
|
|
TakeOrder
|
||
|
|
PackageItems
|
||
|
|
CollectPayment
|
||
|
|
}
|
||
|
|
|
||
|
|
then restock {
|
||
|
|
if(display_low)
|
||
|
|
FetchFromKitchen
|
||
|
|
ArrangeOnShelves
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check ovens between customers
|
||
|
|
timeout(5s) {
|
||
|
|
CheckAllOvens
|
||
|
|
}
|
||
|
|
|
||
|
|
PrepareNextBatch
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This tree repeats forever: serve a customer or restock the display, check the ovens, and prepare the next batch.
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
You now know the full toolkit for behavior trees. In [Advanced Behaviors](./05-advanced-behaviors.md), you will learn patterns for building complex AI systems with nested trees, state-based switching, and modular design.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Reference**: See [Decorators Reference](../reference/12-decorators.md) for all decorator types and [Expression Language](../reference/17-expressions.md) for complete condition syntax.
|