Skip to content

快速开始

本教程将引导你在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. 学习核心概念深入理解行为树原理
  2. 尝试编辑器使用指南可视化创建行为树
  3. 查看高级用法了解更多功能

常见问题

为什么行为树不执行?

确保:

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

Released under the MIT License.