fix(behavior-tree): 修复插件节点执行问题并完善文档
This commit is contained in:
484
docs/guide/behavior-tree/core-concepts.md
Normal file
484
docs/guide/behavior-tree/core-concepts.md
Normal file
@@ -0,0 +1,484 @@
|
||||
# 核心概念
|
||||
|
||||
本文介绍行为树系统的核心概念和工作原理。
|
||||
|
||||
## 什么是行为树?
|
||||
|
||||
行为树(Behavior Tree)是一种用于控制AI和自动化系统的决策结构。它通过树状层次结构组织任务,从根节点开始逐层执行,直到找到合适的行为。
|
||||
|
||||
### 与状态机的对比
|
||||
|
||||
传统状态机:
|
||||
- 基于状态和转换
|
||||
- 状态之间的转换复杂
|
||||
- 难以扩展和维护
|
||||
- 不便于复用
|
||||
|
||||
行为树:
|
||||
- 基于任务和层次结构
|
||||
- 模块化、易于复用
|
||||
- 可视化编辑
|
||||
- 灵活的决策逻辑
|
||||
|
||||
|
||||
## 树结构
|
||||
|
||||
行为树由节点组成,形成树状结构:
|
||||
|
||||
```
|
||||
Root (根节点)
|
||||
├── Selector (选择器)
|
||||
│ ├── Sequence (序列)
|
||||
│ │ ├── Condition (条件)
|
||||
│ │ └── Action (动作)
|
||||
│ └── Action (动作)
|
||||
└── Sequence (序列)
|
||||
├── Action (动作)
|
||||
└── Wait (等待)
|
||||
```
|
||||
|
||||
每个节点都有:
|
||||
- 父节点(除了根节点)
|
||||
- 零个或多个子节点
|
||||
- 执行状态
|
||||
- 返回结果
|
||||
|
||||
|
||||
## 节点类型
|
||||
|
||||
### 复合节点(Composite)
|
||||
|
||||
复合节点有多个子节点,按特定规则执行它们。
|
||||
|
||||
#### Selector(选择器)
|
||||
|
||||
按顺序尝试执行子节点,直到某个子节点成功。
|
||||
|
||||
```typescript
|
||||
.selector('FindFood')
|
||||
.action('EatNearbyFood', () => {
|
||||
if (hasNearbyFood()) {
|
||||
eat();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
return TaskStatus.Failure;
|
||||
})
|
||||
.action('SearchFood', () => {
|
||||
searchForFood();
|
||||
return TaskStatus.Running;
|
||||
})
|
||||
.action('GiveUp', () => {
|
||||
return TaskStatus.Failure;
|
||||
})
|
||||
.end()
|
||||
```
|
||||
|
||||
执行逻辑:
|
||||
1. 尝试第一个子节点
|
||||
2. 如果返回Success,选择器成功
|
||||
3. 如果返回Failure,尝试下一个子节点
|
||||
4. 如果返回Running,选择器返回Running
|
||||
5. 所有子节点都失败时,选择器失败
|
||||
|
||||
|
||||
#### Sequence(序列)
|
||||
|
||||
按顺序执行所有子节点,直到某个子节点失败。
|
||||
|
||||
```typescript
|
||||
.sequence('AttackSequence')
|
||||
.condition((e, bb) => hasTarget()) // 检查是否有目标
|
||||
.action('Aim', () => TaskStatus.Success) // 瞄准
|
||||
.action('Fire', () => TaskStatus.Success) // 开火
|
||||
.end()
|
||||
```
|
||||
|
||||
执行逻辑:
|
||||
1. 依次执行子节点
|
||||
2. 如果子节点返回Failure,序列失败
|
||||
3. 如果子节点返回Running,序列返回Running
|
||||
4. 如果子节点返回Success,继续下一个子节点
|
||||
5. 所有子节点都成功时,序列成功
|
||||
|
||||
|
||||
#### Parallel(并行)
|
||||
|
||||
同时执行多个子节点。
|
||||
|
||||
```typescript
|
||||
import { ParallelPolicy } from '@esengine/behavior-tree';
|
||||
|
||||
.parallel(ParallelPolicy.RequireAll) // 所有任务都要成功
|
||||
.action('PlayAnimation', () => TaskStatus.Success)
|
||||
.action('PlaySound', () => TaskStatus.Success)
|
||||
.action('SpawnEffect', () => TaskStatus.Success)
|
||||
.end()
|
||||
```
|
||||
|
||||
策略类型:
|
||||
- `RequireAll`:所有子节点都成功才成功
|
||||
- `RequireOne`:任意一个子节点成功就成功
|
||||
|
||||
|
||||
### 装饰器节点(Decorator)
|
||||
|
||||
装饰器节点只有一个子节点,用于修改子节点的行为或结果。
|
||||
|
||||
#### Inverter(反转)
|
||||
|
||||
反转子节点的结果:
|
||||
|
||||
```typescript
|
||||
.inverter()
|
||||
.condition((e, bb) => isEnemyNearby()) // 检查敌人是否附近
|
||||
.end()
|
||||
// 如果有敌人返回false,没有敌人返回true
|
||||
```
|
||||
|
||||
#### Repeater(重复)
|
||||
|
||||
重复执行子节点:
|
||||
|
||||
```typescript
|
||||
.repeat(3) // 重复3次
|
||||
.action('Jump', () => TaskStatus.Success)
|
||||
.end()
|
||||
```
|
||||
|
||||
#### Cooldown(冷却)
|
||||
|
||||
限制子节点的执行频率:
|
||||
|
||||
```typescript
|
||||
.cooldown(5.0) // 5秒冷却
|
||||
.action('UseSpecialAbility', () => {
|
||||
console.log('使用特殊技能');
|
||||
return TaskStatus.Success;
|
||||
})
|
||||
.end()
|
||||
```
|
||||
|
||||
#### Timeout(超时)
|
||||
|
||||
限制子节点的执行时间:
|
||||
|
||||
```typescript
|
||||
.timeout(10.0) // 10秒超时
|
||||
.action('ComplexTask', () => {
|
||||
// 长时间运行的任务
|
||||
return TaskStatus.Running;
|
||||
})
|
||||
.end()
|
||||
```
|
||||
|
||||
|
||||
### 叶节点(Leaf)
|
||||
|
||||
叶节点没有子节点,执行具体的任务。
|
||||
|
||||
#### Action(动作)
|
||||
|
||||
执行具体操作:
|
||||
|
||||
```typescript
|
||||
.action('Move', (entity, blackboard, deltaTime) => {
|
||||
const target = blackboard?.getValue('targetPosition');
|
||||
|
||||
if (!target) {
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// 移动逻辑
|
||||
const moved = moveTowards(target, deltaTime);
|
||||
|
||||
if (moved) {
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
return TaskStatus.Running;
|
||||
})
|
||||
```
|
||||
|
||||
#### Condition(条件)
|
||||
|
||||
检查条件:
|
||||
|
||||
```typescript
|
||||
.condition((entity, blackboard) => {
|
||||
const health = blackboard?.getValue('health');
|
||||
return health > 50;
|
||||
}, 'CheckHealthHigh')
|
||||
```
|
||||
|
||||
#### Wait(等待)
|
||||
|
||||
等待指定时间:
|
||||
|
||||
```typescript
|
||||
.wait(2.0) // 等待2秒
|
||||
```
|
||||
|
||||
|
||||
## 任务状态
|
||||
|
||||
每个节点执行后返回以下状态之一:
|
||||
|
||||
### Success(成功)
|
||||
|
||||
任务成功完成。
|
||||
|
||||
```typescript
|
||||
.action('CollectCoin', () => {
|
||||
coin.collect();
|
||||
return TaskStatus.Success;
|
||||
})
|
||||
```
|
||||
|
||||
### Failure(失败)
|
||||
|
||||
任务执行失败。
|
||||
|
||||
```typescript
|
||||
.condition((e, bb) => {
|
||||
const hasKey = bb?.getValue('hasKey');
|
||||
return hasKey ? TaskStatus.Success : TaskStatus.Failure;
|
||||
})
|
||||
```
|
||||
|
||||
### Running(运行中)
|
||||
|
||||
任务需要多帧完成,仍在执行中。
|
||||
|
||||
```typescript
|
||||
.action('ChargeLaser', (entity, blackboard, deltaTime) => {
|
||||
let chargeTime = blackboard?.getValue('chargeTime') || 0;
|
||||
chargeTime += deltaTime;
|
||||
blackboard?.setValue('chargeTime', chargeTime);
|
||||
|
||||
if (chargeTime >= 3.0) {
|
||||
// 充能完成
|
||||
blackboard?.setValue('chargeTime', 0);
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
return TaskStatus.Running; // 继续充能
|
||||
})
|
||||
```
|
||||
|
||||
### Invalid(无效)
|
||||
|
||||
节点未初始化或已重置。通常不需要手动返回此状态。
|
||||
|
||||
|
||||
## 黑板系统
|
||||
|
||||
黑板(Blackboard)是行为树的数据存储系统,用于在节点之间共享数据。
|
||||
|
||||
### 本地黑板
|
||||
|
||||
每个行为树实例都有自己的本地黑板:
|
||||
|
||||
```typescript
|
||||
const ai = BehaviorTreeBuilder.create(scene, 'EnemyAI')
|
||||
.blackboard()
|
||||
.defineVariable('health', BlackboardValueType.Number, 100)
|
||||
.defineVariable('target', BlackboardValueType.Object, null)
|
||||
.defineVariable('state', BlackboardValueType.String, 'idle')
|
||||
.endBlackboard()
|
||||
.build();
|
||||
```
|
||||
|
||||
### 支持的数据类型
|
||||
|
||||
```typescript
|
||||
import { BlackboardValueType } from '@esengine/behavior-tree';
|
||||
|
||||
.blackboard()
|
||||
.defineVariable('count', BlackboardValueType.Number, 0)
|
||||
.defineVariable('name', BlackboardValueType.String, 'Enemy')
|
||||
.defineVariable('isActive', BlackboardValueType.Boolean, true)
|
||||
.defineVariable('position', BlackboardValueType.Vector2, { x: 0, y: 0 })
|
||||
.defineVariable('direction', BlackboardValueType.Vector3, { x: 0, y: 0, z: 0 })
|
||||
.defineVariable('data', BlackboardValueType.Object, {})
|
||||
.defineVariable('items', BlackboardValueType.Array, [])
|
||||
.endBlackboard()
|
||||
```
|
||||
|
||||
### 读写变量
|
||||
|
||||
```typescript
|
||||
.action('UseBlackboard', (entity, blackboard) => {
|
||||
// 读取变量
|
||||
const health = blackboard?.getValue('health');
|
||||
const target = blackboard?.getValue('target');
|
||||
|
||||
// 写入变量
|
||||
blackboard?.setValue('health', health - 10);
|
||||
blackboard?.setValue('lastAttackTime', Date.now());
|
||||
|
||||
// 检查变量是否存在
|
||||
if (blackboard?.hasVariable('powerup')) {
|
||||
const powerup = blackboard.getValue('powerup');
|
||||
console.log('已获得强化:', powerup);
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
})
|
||||
```
|
||||
|
||||
### 全局黑板
|
||||
|
||||
所有行为树实例共享的黑板:
|
||||
|
||||
```typescript
|
||||
import { GlobalBlackboard } from '@esengine/behavior-tree';
|
||||
|
||||
// 设置全局变量
|
||||
GlobalBlackboard.setValue('gameState', 'playing');
|
||||
GlobalBlackboard.setValue('difficulty', 5);
|
||||
|
||||
// 在任何行为树中访问
|
||||
.action('CheckGlobalState', () => {
|
||||
const gameState = GlobalBlackboard.getValue('gameState');
|
||||
|
||||
if (gameState === 'paused') {
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## 执行流程
|
||||
|
||||
### 初始化
|
||||
|
||||
```typescript
|
||||
// 1. 初始化Core和场景
|
||||
Core.create();
|
||||
const scene = new Scene();
|
||||
Core.setScene(scene);
|
||||
|
||||
// 2. 构建行为树
|
||||
const ai = BehaviorTreeBuilder.create(scene, 'AI')
|
||||
// ... 定义节点
|
||||
.build();
|
||||
|
||||
// 3. 启动
|
||||
BehaviorTreeStarter.start(ai);
|
||||
```
|
||||
|
||||
### 更新循环
|
||||
|
||||
```typescript
|
||||
// 每帧更新
|
||||
gameLoop(() => {
|
||||
const deltaTime = getDeltaTime();
|
||||
Core.update(deltaTime); // Core会自动更新场景和所有行为树
|
||||
});
|
||||
```
|
||||
|
||||
### 执行顺序
|
||||
|
||||
```
|
||||
1. 从根节点开始
|
||||
2. 根节点执行其逻辑(通常是Selector或Sequence)
|
||||
3. 根节点的子节点按顺序执行
|
||||
4. 每个子节点可能有自己的子节点
|
||||
5. 叶节点执行具体操作并返回状态
|
||||
6. 状态向上传播到父节点
|
||||
7. 父节点根据策略决定如何处理子节点的状态
|
||||
8. 最终根节点返回整体状态
|
||||
```
|
||||
|
||||
### 执行示例
|
||||
|
||||
```typescript
|
||||
const tree = BehaviorTreeBuilder.create(scene, 'Example')
|
||||
.selector('Root') // 1. 执行选择器
|
||||
.sequence('Branch1') // 2. 尝试第一个分支
|
||||
.condition(() => false) // 3. 条件失败
|
||||
.end() // 4. 序列失败,选择器继续下一个分支
|
||||
.sequence('Branch2') // 5. 尝试第二个分支
|
||||
.condition(() => true) // 6. 条件成功
|
||||
.action(() => TaskStatus.Success) // 7. 动作成功
|
||||
.end() // 8. 序列成功,选择器成功
|
||||
.end() // 9. 整个树成功
|
||||
.build();
|
||||
```
|
||||
|
||||
执行流程图:
|
||||
|
||||
```
|
||||
Root(Selector)
|
||||
→ Branch1(Sequence)
|
||||
→ Condition: Failure
|
||||
→ Branch1 fails
|
||||
→ Branch2(Sequence)
|
||||
→ Condition: Success
|
||||
→ Action: Success
|
||||
→ Branch2 succeeds
|
||||
→ Root succeeds
|
||||
```
|
||||
|
||||
|
||||
## ECS集成
|
||||
|
||||
本框架的行为树完全基于ECS架构:
|
||||
|
||||
### 节点即实体
|
||||
|
||||
每个行为树节点都是一个Entity:
|
||||
|
||||
```typescript
|
||||
// 行为树节点在内部被表示为:
|
||||
const nodeEntity = scene.createEntity('SelectorNode');
|
||||
nodeEntity.addComponent(new SelectorComponent());
|
||||
nodeEntity.addComponent(new ParentComponent(parentEntity));
|
||||
```
|
||||
|
||||
### 组件存储数据
|
||||
|
||||
节点属性存储在组件中:
|
||||
|
||||
```typescript
|
||||
// Action节点的数据组件
|
||||
class ActionComponent extends Component {
|
||||
actionFunc: ActionFunction;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Blackboard组件
|
||||
class BlackboardComponent extends Component {
|
||||
private variables: Map<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
### 系统驱动行为
|
||||
|
||||
行为树系统负责更新所有节点:
|
||||
|
||||
```typescript
|
||||
class BehaviorTreeSystem extends EntitySystem {
|
||||
update() {
|
||||
// 更新所有活跃的行为树
|
||||
for (const entity of this.entities) {
|
||||
const root = entity.getComponent(BehaviorTreeRootComponent);
|
||||
if (root && root.isActive) {
|
||||
this.updateNode(root.rootEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 下一步
|
||||
|
||||
现在你已经理解了行为树的核心概念,接下来可以:
|
||||
|
||||
- 查看[快速开始](./getting-started.md)创建第一个行为树
|
||||
- 学习[编辑器使用指南](./editor-guide.md)可视化创建行为树
|
||||
- 探索[高级用法](./advanced-usage.md)了解更多功能
|
||||
- 阅读[最佳实践](./best-practices.md)学习设计模式
|
||||
Reference in New Issue
Block a user