快速开始
本教程将引导你在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:无效状态(未初始化或已重置)
下一步
现在你已经创建了第一个行为树,接下来可以:
常见问题
为什么行为树不执行?
确保:
- 已经安装了
BehaviorTreePlugin - 调用了
plugin.setupScene(scene) - 调用了
BehaviorTreeStarter.start(aiRoot) - 场景的
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);