跳转到内容

高级用法

本文介绍行为树系统的高级功能和使用技巧。

全局黑板在所有行为树实例之间共享数据。

import { GlobalBlackboardService } from '@esengine/behavior-tree';
import { Core } from '@esengine/ecs-framework';
// 获取全局黑板服务
const globalBlackboard = Core.services.resolve(GlobalBlackboardService);
// 设置全局变量
globalBlackboard.setValue('gameState', 'playing');
globalBlackboard.setValue('playerCount', 4);
globalBlackboard.setValue('difficulty', 'hard');
// 读取全局变量
const gameState = globalBlackboard.getValue('gameState');
const playerCount = globalBlackboard.getValue<number>('playerCount');

在自定义执行器中访问全局黑板

Section titled “在自定义执行器中访问全局黑板”
import { INodeExecutor, NodeExecutionContext, BindingHelper } from '@esengine/behavior-tree';
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;
}
}

对于不需要每帧更新的AI,可以使用冷却装饰器:

// 每0.1秒执行一次
const ai = BehaviorTreeBuilder.create('ThrottledAI')
.cooldown(0.1, 'ThrottleRoot')
.selector('MainLogic')
// AI逻辑...
.end()
.end()
.build();

在自定义执行器中缓存昂贵的条件检查结果:

export class CachedCheck implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime, totalTime } = context;
const cacheTime = state.lastCheckTime || 0;
// 如果缓存未过期(1秒内),直接使用缓存结果
if (totalTime - cacheTime < 1.0) {
return state.cachedResult || TaskStatus.Failure;
}
// 执行昂贵的检查
const result = performExpensiveCheck();
const status = result ? TaskStatus.Success : TaskStatus.Failure;
// 缓存结果
state.cachedResult = status;
state.lastCheckTime = totalTime;
return status;
}
reset(context: NodeExecutionContext): void {
context.state.cachedResult = undefined;
context.state.lastCheckTime = undefined;
}
}

将大量计算分散到多帧:

export class ProcessLargeDataset implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { state, runtime } = context;
const data = runtime.getBlackboardValue<any[]>('dataset') || [];
let processedIndex = state.processedIndex || 0;
const batchSize = 100; // 每帧处理100个
const endIndex = Math.min(processedIndex + batchSize, data.length);
for (let i = processedIndex; i < endIndex; i++) {
processItem(data[i]);
}
state.processedIndex = endIndex;
if (endIndex >= data.length) {
return TaskStatus.Success;
}
return TaskStatus.Running;
}
reset(context: NodeExecutionContext): void {
context.state.processedIndex = 0;
}
}

在关键位置添加日志:

const tree = BehaviorTreeBuilder.create('Debug')
.log('开始战斗序列', 'StartCombat')
.sequence('Combat')
.log('检查生命值', 'CheckHealth')
.blackboardCompare('health', 0, 'greater')
.log('执行攻击', 'Attack')
.end()
.build();
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 输出所有黑板变量
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
// 输出活动节点
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
export class DebugAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const { nodeData, runtime, state } = context;
console.log(`[${nodeData.name}] 开始执行`);
console.log('配置:', nodeData.config);
console.log('状态:', state);
console.log('黑板:', runtime.getAllBlackboardVariables());
// 执行逻辑...
return TaskStatus.Success;
}
}

测量节点执行时间:

export class ProfiledAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const startTime = performance.now();
// 执行操作
doSomething();
const elapsed = performance.now() - startTime;
console.log(`[${context.nodeData.name}] 耗时: ${elapsed.toFixed(2)}ms`);
return TaskStatus.Success;
}
}

使用行为树实现状态机:

const fsm = BehaviorTreeBuilder.create('StateMachine')
.defineBlackboardVariable('currentState', 'idle')
.selector('StateSwitch')
// Idle状态
.sequence('IdleState')
.blackboardCompare('currentState', 'idle', 'equals')
.log('执行Idle行为', 'IdleBehavior')
.end()
// Move状态
.sequence('MoveState')
.blackboardCompare('currentState', 'move', 'equals')
.log('执行Move行为', 'MoveBehavior')
.end()
// Attack状态
.sequence('AttackState')
.blackboardCompare('currentState', 'attack', 'equals')
.log('执行Attack行为', 'AttackBehavior')
.end()
.end()
.build();

状态转换通过修改黑板变量实现:

const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('currentState', 'move');

按优先级执行任务:

const tree = BehaviorTreeBuilder.create('PriorityQueue')
.selector('Priorities')
// 最高优先级:生存
.sequence('Survive')
.blackboardCompare('health', 20, 'less')
.log('治疗', 'Heal')
.end()
// 中优先级:战斗
.sequence('Combat')
.blackboardExists('nearbyEnemy')
.log('战斗', 'Fight')
.end()
// 低优先级:收集资源
.sequence('Gather')
.log('收集资源', 'CollectResources')
.end()
.end()
.build();

同时执行多个任务:

const tree = BehaviorTreeBuilder.create('ParallelTasks')
.parallel('Effects', { successPolicy: 'all' })
.log('播放动画', 'PlayAnimation')
.log('播放音效', 'PlaySound')
.log('生成粒子', 'SpawnParticles')
.end()
.build();

失败时重试:

// 使用自定义重试装饰器(参见custom-actions.md中的RetryDecorator示例)
// 或者使用UntilSuccess装饰器
const tree = BehaviorTreeBuilder.create('Retry')
.untilSuccess('RetryUntilSuccess')
.log('尝试操作', 'TryOperation')
.end()
.build();

限制任务执行时间:

const tree = BehaviorTreeBuilder.create('Timeout')
.timeout(5.0, 'TimeLimit')
.log('长时间运行的任务', 'LongTask')
.end()
.build();

参见Cocos Creator集成指南

参见LayaAir集成指南

// 好的做法:使用类型化的黑板访问
const health = runtime.getBlackboardValue<number>('health');
// 好的做法:定义所有黑板变量
const tree = BehaviorTreeBuilder.create('AI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('state', 'idle')
// ...
// 不好:嵌套过深
.selector()
.sequence()
.selector()
.sequence()
.selector()
// 太深了!
.end()
.end()
.end()
.end()
.end()
// 好:使用合理的深度
.selector()
.sequence()
.log('Action1')
.log('Action2')
.end()
.sequence()
.log('Action3')
.log('Action4')
.end()
.end()
// 好的做法
.selector('CombatDecision')
.sequence('AttackEnemy')
.blackboardExists('target', 'HasTarget')
.log('执行攻击', 'Attack')
.end()
.end()
// 不好的做法
.selector('Node1')
.sequence('Node2')
.blackboardExists('target', 'Node3')
.log('Attack', 'Node4')
.end()
.end()

将复杂逻辑分解为多个独立的行为树,在需要时组合使用。

  • 避免在每帧执行昂贵的操作
  • 使用冷却装饰器控制执行频率
  • 缓存计算结果
  • 合理使用并行节点