Files
esengine/docs/guide/behavior-tree/getting-started.md

314 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 快速开始
本教程将引导你在5分钟内创建第一个行为树。
## 安装
```bash
npm install @esengine/behavior-tree
```
## 第一个行为树
让我们创建一个简单的AI行为树实现"巡逻-发现敌人-攻击"的逻辑。
### 步骤1导入依赖
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin,
BlackboardValueType,
TaskStatus,
CompareOperator
} from '@esengine/behavior-tree';
```
### 步骤2安装插件
```typescript
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
```
### 步骤3创建场景并设置行为树系统
```typescript
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
```
### 步骤4构建行为树
```typescript
const guardAI = BehaviorTreeBuilder.create(scene, 'GuardAI')
// 定义黑板变量
.blackboard()
.defineVariable('health', BlackboardValueType.Number, 100)
.defineVariable('hasEnemy', BlackboardValueType.Boolean, false)
.defineVariable('patrolPoint', BlackboardValueType.Number, 0)
.endBlackboard()
// 根选择器
.selector('RootSelector')
// 分支1如果发现敌人且生命值高则攻击
.sequence('CombatBranch')
.checkBlackboardExists('hasEnemy', true, 'CheckEnemy')
.compareBlackboardValue('health', CompareOperator.Greater, 30, 'CheckHealth')
.action('Attack', (entity, blackboard) => {
console.log('守卫正在攻击敌人');
// 模拟攻击逻辑
const health = blackboard?.getValue<number>('health') || 100;
blackboard?.setValue('health', health - 10);
return TaskStatus.Success;
})
.end()
// 分支2如果生命值低则逃跑
.sequence('FleeBranch')
.compareBlackboardValue('health', CompareOperator.LessOrEqual, 30)
.action('Flee', (entity) => {
console.log('守卫生命值过低,正在逃跑');
return TaskStatus.Success;
})
.end()
// 分支3默认巡逻
.sequence('PatrolBranch')
.action('MoveToNextPoint', (entity, blackboard) => {
const point = blackboard?.getValue<number>('patrolPoint') || 0;
const nextPoint = (point + 1) % 4;
blackboard?.setValue('patrolPoint', nextPoint);
console.log(`守卫移动到巡逻点 ${nextPoint}`);
return TaskStatus.Success;
})
.wait(2.0, 'WaitAtPoint') // 在巡逻点等待2秒
.end()
.end()
.build();
```
### 步骤5启动行为树
```typescript
BehaviorTreeStarter.start(guardAI);
```
### 步骤6运行游戏循环
```typescript
// 模拟游戏循环
setInterval(() => {
Core.update(0.1); // 传入deltaTime(秒)
}, 100); // 每100ms更新一次
```
## 完整代码
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin,
BlackboardValueType,
TaskStatus,
CompareOperator
} from '@esengine/behavior-tree';
async function main() {
// 创建核心和场景
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 构建行为树
const guardAI = BehaviorTreeBuilder.create(scene, 'GuardAI')
.blackboard()
.defineVariable('health', BlackboardValueType.Number, 100)
.defineVariable('hasEnemy', BlackboardValueType.Boolean, false)
.defineVariable('patrolPoint', BlackboardValueType.Number, 0)
.endBlackboard()
.selector('RootSelector')
.sequence('CombatBranch')
.checkBlackboardExists('hasEnemy', true)
.compareBlackboardValue('health', CompareOperator.Greater, 30)
.action('Attack', (entity, blackboard) => {
console.log('守卫正在攻击敌人');
const health = blackboard?.getValue<number>('health') || 100;
blackboard?.setValue('health', health - 10);
return TaskStatus.Success;
})
.end()
.sequence('FleeBranch')
.compareBlackboardValue('health', CompareOperator.LessOrEqual, 30)
.action('Flee', () => {
console.log('守卫生命值过低,正在逃跑');
return TaskStatus.Success;
})
.end()
.sequence('PatrolBranch')
.action('MoveToNextPoint', (entity, blackboard) => {
const point = blackboard?.getValue<number>('patrolPoint') || 0;
const nextPoint = (point + 1) % 4;
blackboard?.setValue('patrolPoint', nextPoint);
console.log(`守卫移动到巡逻点 ${nextPoint}`);
return TaskStatus.Success;
})
.wait(2.0)
.end()
.end()
.build();
// 启动AI
BehaviorTreeStarter.start(guardAI);
// 运行游戏循环
setInterval(() => {
Core.update(0.1); // 传入deltaTime(秒)
}, 100);
// 5秒后模拟发现敌人
setTimeout(() => {
const blackboard = guardAI.getComponent(BlackboardComponent);
blackboard?.setValue('hasEnemy', true);
console.log('发现敌人!');
}, 5000);
}
main();
```
## 运行结果
运行程序后,你会看到类似的输出:
```
守卫移动到巡逻点 1
守卫移动到巡逻点 2
守卫移动到巡逻点 3
发现敌人!
守卫正在攻击敌人
守卫正在攻击敌人
守卫正在攻击敌人
...
守卫生命值过低,正在逃跑
```
## 理解代码
### 黑板变量
```typescript
.blackboard()
.defineVariable('health', BlackboardValueType.Number, 100)
.defineVariable('hasEnemy', BlackboardValueType.Boolean, false)
.defineVariable('patrolPoint', BlackboardValueType.Number, 0)
.endBlackboard()
```
黑板用于在节点之间共享数据。这里定义了三个变量:
- `health`:守卫的生命值
- `hasEnemy`:是否发现敌人
- `patrolPoint`:当前巡逻点编号
### 选择器节点
```typescript
.selector('RootSelector')
// 分支1
// 分支2
// 分支3
.end()
```
选择器按顺序尝试执行子节点,直到某个子节点返回成功。类似于编程中的 `if-else if-else`
### 序列节点
```typescript
.sequence('CombatBranch')
.checkBlackboardExists('hasEnemy', true)
.compareBlackboardValue('health', CompareOperator.Greater, 30)
.action('Attack', ...)
.end()
```
序列节点按顺序执行所有子节点,如果任何一个失败则整个序列失败。类似于编程中的 `&&` 运算符。
### 自定义动作
```typescript
.action('Attack', (entity, blackboard, deltaTime) => {
// 你的自定义逻辑
console.log('执行攻击');
return TaskStatus.Success;
})
```
动作节点执行具体的操作并返回状态:
- `TaskStatus.Success`:成功完成
- `TaskStatus.Failure`:执行失败
- `TaskStatus.Running`:正在执行(需要多帧完成)
## 常见任务状态
行为树的每个节点返回以下状态之一:
- **Success**:任务成功完成
- **Failure**:任务执行失败
- **Running**:任务正在执行,需要在后续帧继续
- **Invalid**:无效状态(未初始化或已重置)
## 下一步
现在你已经创建了第一个行为树,接下来可以:
1. 学习[核心概念](./core-concepts.md)深入理解行为树原理
2. 尝试[编辑器使用指南](./editor-guide.md)可视化创建行为树
3. 查看[高级用法](./advanced-usage.md)了解更多功能
## 常见问题
### 为什么行为树不执行?
确保:
1. 已经安装了 `BehaviorTreePlugin`
2. 调用了 `plugin.setupScene(scene)`
3. 调用了 `BehaviorTreeStarter.start(aiRoot)`
4. 场景的 `update()` 方法在游戏循环中被调用
### 如何调试行为树?
使用日志动作和控制台输出:
```typescript
.log('到达这个节点', 'info')
.action('MyAction', (entity, blackboard) => {
console.log('blackboard:', blackboard?.getAllVariables());
return TaskStatus.Success;
})
```
### 如何停止行为树?
```typescript
BehaviorTreeStarter.stop(aiRoot);
```
或暂停后恢复:
```typescript
BehaviorTreeStarter.pause(aiRoot);
// ... 一段时间后
BehaviorTreeStarter.resume(aiRoot);
```