核心概念
本文介绍行为树系统的核心概念和工作原理。
什么是行为树?
Section titled “什么是行为树?”行为树(Behavior Tree)是一种用于控制AI和自动化系统的决策结构。它通过树状层次结构组织任务,从根节点开始逐层执行,直到找到合适的行为。
与状态机的对比
Section titled “与状态机的对比”传统状态机:
- 基于状态和转换
- 状态之间的转换复杂
- 难以扩展和维护
- 不便于复用
行为树:
- 基于任务和层次结构
- 模块化、易于复用
- 可视化编辑
- 灵活的决策逻辑
行为树由节点组成,形成树状结构:
Root (根节点)├── Selector (选择器)│ ├── Sequence (序列)│ │ ├── Condition (条件)│ │ └── Action (动作)│ └── Action (动作)└── Sequence (序列) ├── Action (动作) └── Wait (等待)每个节点都有:
- 父节点(除了根节点)
- 零个或多个子节点
- 执行状态
- 返回结果
复合节点(Composite)
Section titled “复合节点(Composite)”复合节点有多个子节点,按特定规则执行它们。
Selector(选择器)
Section titled “Selector(选择器)”按顺序尝试执行子节点,直到某个子节点成功。
const tree = BehaviorTreeBuilder.create('FindFood') .selector('FindFoodSelector') .log('尝试吃附近的食物', 'EatNearby') .log('搜索食物', 'SearchFood') .log('放弃', 'GiveUp') .end() .build();执行逻辑:
- 尝试第一个子节点
- 如果返回Success,选择器成功
- 如果返回Failure,尝试下一个子节点
- 如果返回Running,选择器返回Running
- 所有子节点都失败时,选择器失败
Sequence(序列)
Section titled “Sequence(序列)”按顺序执行所有子节点,直到某个子节点失败。
const tree = BehaviorTreeBuilder.create('Attack') .sequence('AttackSequence') .blackboardExists('target') // 检查是否有目标 .log('瞄准', 'Aim') .log('开火', 'Fire') .end() .build();执行逻辑:
- 依次执行子节点
- 如果子节点返回Failure,序列失败
- 如果子节点返回Running,序列返回Running
- 如果子节点返回Success,继续下一个子节点
- 所有子节点都成功时,序列成功
Parallel(并行)
Section titled “Parallel(并行)”同时执行多个子节点。
const tree = BehaviorTreeBuilder.create('PlayEffects') .parallel('Effects', { successPolicy: 'all', // 所有任务都要成功 failurePolicy: 'one' // 任一失败则失败 }) .log('播放动画', 'PlayAnimation') .log('播放音效', 'PlaySound') .log('生成粒子', 'SpawnEffect') .end() .build();策略类型:
successPolicy: 'all': 所有子节点都成功才成功successPolicy: 'one': 任意一个子节点成功就成功failurePolicy: 'all': 所有子节点都失败才失败failurePolicy: 'one': 任意一个子节点失败就失败
装饰器节点(Decorator)
Section titled “装饰器节点(Decorator)”装饰器节点只有一个子节点,用于修改子节点的行为或结果。
Inverter(反转)
Section titled “Inverter(反转)”反转子节点的结果:
const tree = BehaviorTreeBuilder.create('CheckSafe') .inverter('NotHasEnemy') .blackboardExists('enemy') .end() .build();Repeater(重复)
Section titled “Repeater(重复)”重复执行子节点:
const tree = BehaviorTreeBuilder.create('Jump3Times') .repeater(3, 'RepeatJump') .log('跳跃', 'Jump') .end() .build();Cooldown(冷却)
Section titled “Cooldown(冷却)”限制子节点的执行频率:
const tree = BehaviorTreeBuilder.create('UseSkill') .cooldown(5.0, 'SkillCooldown') .log('使用特殊技能', 'UseSpecialAbility') .end() .build();Timeout(超时)
Section titled “Timeout(超时)”限制子节点的执行时间:
const tree = BehaviorTreeBuilder.create('TimedTask') .timeout(10.0, 'TaskTimeout') .log('长时间运行的任务', 'ComplexTask') .end() .build();叶节点(Leaf)
Section titled “叶节点(Leaf)”叶节点没有子节点,执行具体的任务。
Action(动作)
Section titled “Action(动作)”执行具体操作。内置动作节点包括:
const tree = BehaviorTreeBuilder.create('Actions') .sequence() .wait(2.0) // 等待2秒 .log('Hello', 'LogAction') // 输出日志 .setBlackboardValue('score', 100) // 设置黑板值 .modifyBlackboardValue('score', 'add', 10) // 修改黑板值 .end() .build();要实现自定义动作,需要创建自定义执行器,参见自定义节点执行器。
Condition(条件)
Section titled “Condition(条件)”检查条件。内置条件节点包括:
const tree = BehaviorTreeBuilder.create('Conditions') .selector() .blackboardExists('player') // 检查变量是否存在 .blackboardCompare('health', 50, 'greater') // 比较变量值 .randomProbability(0.5) // 50%概率 .end() .build();Wait(等待)
Section titled “Wait(等待)”等待指定时间:
const tree = BehaviorTreeBuilder.create('WaitExample') .wait(2.0, 'Wait2Seconds') .build();每个节点执行后返回以下状态之一:
Success(成功)
Section titled “Success(成功)”任务成功完成。
// 内置节点会根据逻辑自动返回Success.log('任务完成') // 总是返回Success.blackboardCompare('score', 100, 'greater') // 条件满足时返回SuccessFailure(失败)
Section titled “Failure(失败)”任务执行失败。
.blackboardCompare('score', 100, 'greater') // 条件不满足返回Failure.blackboardExists('nonExistent') // 变量不存在返回FailureRunning(运行中)
Section titled “Running(运行中)”任务需要多帧完成,仍在执行中。
.wait(3.0) // 等待过程中返回Running,3秒后返回SuccessInvalid(无效)
Section titled “Invalid(无效)”节点未初始化或已重置。通常不需要手动处理此状态。
黑板(Blackboard)是行为树的数据存储系统,用于在节点之间共享数据。
每个行为树实例都有自己的本地黑板:
const tree = BehaviorTreeBuilder.create('EnemyAI') .defineBlackboardVariable('health', 100) .defineBlackboardVariable('target', null) .defineBlackboardVariable('state', 'idle') // ... .build();支持的数据类型
Section titled “支持的数据类型”黑板支持以下数据类型:
- String:字符串
- Number:数字
- Boolean:布尔值
- Vector2:二维向量
- Vector3:三维向量
- Object:对象引用
- Array:数组
示例:
const tree = BehaviorTreeBuilder.create('Variables') .defineBlackboardVariable('name', 'Enemy') // 字符串 .defineBlackboardVariable('count', 0) // 数字 .defineBlackboardVariable('isActive', true) // 布尔值 .defineBlackboardVariable('position', { x: 0, y: 0 }) // 对象(也可用于Vector2) .defineBlackboardVariable('velocity', { x: 0, y: 0, z: 0 }) // 对象(也可用于Vector3) .defineBlackboardVariable('items', []) // 数组 .build();通过BehaviorTreeRuntimeComponent访问黑板:
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 读取变量const health = runtime?.getBlackboardValue('health');const target = runtime?.getBlackboardValue('target');
// 写入变量runtime?.setBlackboardValue('health', 50);runtime?.setBlackboardValue('lastAttackTime', Date.now());
// 获取所有变量const allVars = runtime?.getAllBlackboardVariables();也可以使用内置节点操作黑板:
const tree = BehaviorTreeBuilder.create('BlackboardOps') .sequence() .setBlackboardValue('score', 100) // 设置值 .modifyBlackboardValue('score', 'add', 10) // 增加10 .blackboardCompare('score', 110, 'equals') // 检查是否等于110 .end() .build();所有行为树实例共享的黑板,通过GlobalBlackboardService访问:
import { GlobalBlackboardService } from '@esengine/behavior-tree';import { Core } from '@esengine/ecs-framework';
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
// 设置全局变量globalBlackboard.setValue('gameState', 'playing');globalBlackboard.setValue('difficulty', 5);
// 读取全局变量const gameState = globalBlackboard.getValue('gameState');在自定义执行器中访问全局黑板:
import { GlobalBlackboardService } from '@esengine/behavior-tree';import { Core } from '@esengine/ecs-framework';
export class CheckGameState implements INodeExecutor { execute(context: NodeExecutionContext): TaskStatus { const globalBlackboard = Core.services.resolve(GlobalBlackboardService); const gameState = globalBlackboard.getValue('gameState');
if (gameState === 'paused') { return TaskStatus.Failure; }
return TaskStatus.Success; }}// 1. 初始化Core和插件Core.create();const plugin = new BehaviorTreePlugin();await Core.installPlugin(plugin);
// 2. 创建场景const scene = new Scene();plugin.setupScene(scene);Core.setScene(scene);
// 3. 构建行为树const tree = BehaviorTreeBuilder.create('AI') // ... 定义节点 .build();
// 4. 创建实体并启动const entity = scene.createEntity('AIEntity');BehaviorTreeStarter.start(entity, tree);// 每帧更新gameLoop(() => { const deltaTime = getDeltaTime(); Core.update(deltaTime); // Core会自动更新场景和所有行为树});1. 从根节点开始2. 根节点执行其逻辑(通常是Selector或Sequence)3. 根节点的子节点按顺序执行4. 每个子节点可能有自己的子节点5. 叶节点执行具体操作并返回状态6. 状态向上传播到父节点7. 父节点根据策略决定如何处理子节点的状态8. 最终根节点返回整体状态const tree = BehaviorTreeBuilder.create('Example') .selector('Root') // 1. 执行选择器 .sequence('Branch1') // 2. 尝试第一个分支 .blackboardCompare('ready', true, 'equals', 'CheckReady') // 3. 条件失败 .end() // 4. 序列失败,选择器继续下一个分支 .sequence('Branch2') // 5. 尝试第二个分支 .blackboardCompare('active', true, 'equals', 'CheckActive') // 6. 条件成功 .log('执行动作', 'DoAction') // 7. 动作成功 .end() // 8. 序列成功,选择器成功 .end() // 9. 整个树成功 .build();执行流程图:
Root(Selector) → Branch1(Sequence) → CheckReady: Failure → Branch1 fails → Branch2(Sequence) → CheckActive: Success → DoAction: Success → Branch2 succeeds→ Root succeedsRuntime架构
Section titled “Runtime架构”本框架的行为树采用Runtime执行器架构:
- BehaviorTreeData: 纯数据结构,描述行为树的结构和配置
- BehaviorTreeRuntimeComponent: 运行时组件,管理执行状态和黑板
- BehaviorTreeExecutionSystem: 执行系统,驱动行为树运行
- INodeExecutor: 节点执行器接口,定义节点的执行逻辑
- NodeExecutionContext: 执行上下文,包含执行所需的所有信息
- 数据与逻辑分离: BehaviorTreeData是纯数据,执行逻辑在执行器中
- 无状态执行器: 执行器实例可以在多个节点间共享,状态存储在Runtime中
- 类型安全: 通过TypeScript类型系统保证类型安全
- 高性能: 避免不必要的对象创建,优化内存使用
BehaviorTreeBuilder ↓ (构建)BehaviorTreeData ↓ (加载到)BehaviorTreeAssetManager ↓ (读取)BehaviorTreeExecutionSystem ↓ (执行)INodeExecutor.execute(context) ↓ (返回)TaskStatus ↓ (更新)NodeRuntimeState现在你已经理解了行为树的核心概念,接下来可以: