Files
esengine/docs/guide/behavior-tree/advanced-usage.md
yhh ad96edfad0 fix: 恢复 @esengine/ecs-framework 包名
上一个提交错误地将 npm 包名也改了,这里恢复正确的包名。
只更新 GitHub 仓库 URL,不改变 npm 包名。
2025-12-08 21:26:35 +08:00

9.5 KiB

高级用法

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

全局黑板

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

使用全局黑板

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');

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

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;
    }
}

性能优化

1. 降低更新频率

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

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

2. 条件缓存

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

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;
    }
}

3. 分帧执行

将大量计算分散到多帧:

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;
    }
}

调试技巧

1. 使用日志节点

在关键位置添加日志:

const tree = BehaviorTreeBuilder.create('Debug')
    .log('开始战斗序列', 'StartCombat')
    .sequence('Combat')
        .log('检查生命值', 'CheckHealth')
        .blackboardCompare('health', 0, 'greater')
        .log('执行攻击', 'Attack')
    .end()
    .build();

2. 监控黑板状态

const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);

// 输出所有黑板变量
console.log('黑板变量:', runtime?.getAllBlackboardVariables());

// 输出活动节点
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));

3. 在自定义执行器中调试

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;
    }
}

4. 性能分析

测量节点执行时间:

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;
    }
}

常见模式

1. 状态机模式

使用行为树实现状态机:

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');

2. 优先级队列模式

按优先级执行任务:

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();

3. 并行任务模式

同时执行多个任务:

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

4. 重试模式

失败时重试:

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

5. 超时模式

限制任务执行时间:

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

与游戏引擎集成

Cocos Creator集成

参见Cocos Creator集成指南

LayaAir集成

参见LayaAir集成指南

最佳实践

1. 合理使用黑板

// 好的做法:使用类型化的黑板访问
const health = runtime.getBlackboardValue<number>('health');

// 好的做法:定义所有黑板变量
const tree = BehaviorTreeBuilder.create('AI')
    .defineBlackboardVariable('health', 100)
    .defineBlackboardVariable('target', null)
    .defineBlackboardVariable('state', 'idle')
    // ...

2. 避免过深的树结构

// 不好:嵌套过深
.selector()
    .sequence()
        .selector()
            .sequence()
                .selector()
                    // 太深了!
                .end()
            .end()
        .end()
    .end()
.end()

// 好:使用合理的深度
.selector()
    .sequence()
        .log('Action1')
        .log('Action2')
    .end()
    .sequence()
        .log('Action3')
        .log('Action4')
    .end()
.end()

3. 使用有意义的节点名称

// 好的做法
.selector('CombatDecision')
    .sequence('AttackEnemy')
        .blackboardExists('target', 'HasTarget')
        .log('执行攻击', 'Attack')
    .end()
.end()

// 不好的做法
.selector('Node1')
    .sequence('Node2')
        .blackboardExists('target', 'Node3')
        .log('Attack', 'Node4')
    .end()
.end()

4. 模块化设计

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

5. 性能考虑

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

下一步