跳转到内容

快速开始

本教程将引导你在5分钟内创建第一个行为树。

Terminal window
npm install @esengine/behavior-tree

让我们创建一个简单的AI行为树,实现”巡逻-发现敌人-攻击”的逻辑。

import { Core, Scene, Entity } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin
} from '@esengine/behavior-tree';
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);

步骤3: 创建场景并设置行为树系统

Section titled “步骤3: 创建场景并设置行为树系统”
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
const guardAITree = BehaviorTreeBuilder.create('GuardAI')
// 定义黑板变量
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)
// 根选择器
.selector('RootSelector')
// 分支1: 如果发现敌人且生命值高,则攻击
.selector('CombatBranch')
.blackboardExists('hasEnemy', 'CheckEnemy')
.blackboardCompare('health', 30, 'greater', 'CheckHealth')
.log('守卫正在攻击敌人', 'Attack')
.end()
// 分支2: 如果生命值低,则逃跑
.selector('FleeBranch')
.blackboardCompare('health', 30, 'lessOrEqual', 'CheckLowHealth')
.log('守卫生命值过低,正在逃跑', 'Flee')
.end()
// 分支3: 默认巡逻
.selector('PatrolBranch')
.modifyBlackboardValue('patrolPoint', 'add', 1, 'IncrementPatrol')
.log('守卫正在巡逻', 'Patrol')
.wait(2.0, 'WaitAtPoint')
.end()
.end()
.build();
// 创建守卫实体
const guardEntity = scene.createEntity('Guard');
// 启动行为树
BehaviorTreeStarter.start(guardEntity, guardAITree);
// 模拟游戏循环
setInterval(() => {
Core.update(0.1); // 传入deltaTime(秒)
}, 100); // 每100ms更新一次
// 5秒后模拟发现敌人
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
console.log('发现敌人!');
}, 5000);
// 10秒后模拟受伤
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('health', 20);
console.log('守卫受伤!');
}, 10000);
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin,
BehaviorTreeRuntimeComponent
} from '@esengine/behavior-tree';
async function main() {
// 1. 创建核心并安装插件
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// 2. 创建场景
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// 3. 构建行为树数据
const guardAITree = BehaviorTreeBuilder.create('GuardAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)
.selector('RootSelector')
.selector('CombatBranch')
.blackboardExists('hasEnemy')
.blackboardCompare('health', 30, 'greater')
.log('守卫正在攻击敌人')
.end()
.selector('FleeBranch')
.blackboardCompare('health', 30, 'lessOrEqual')
.log('守卫生命值过低,正在逃跑')
.end()
.selector('PatrolBranch')
.modifyBlackboardValue('patrolPoint', 'add', 1)
.log('守卫正在巡逻')
.wait(2.0)
.end()
.end()
.build();
// 4. 创建守卫实体并启动行为树
const guardEntity = scene.createEntity('Guard');
BehaviorTreeStarter.start(guardEntity, guardAITree);
// 5. 运行游戏循环
setInterval(() => {
Core.update(0.1);
}, 100);
// 6. 模拟游戏事件
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
console.log('发现敌人!');
}, 5000);
setTimeout(() => {
const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('health', 20);
console.log('守卫受伤!');
}, 10000);
}
main();

运行程序后,你会看到类似的输出:

守卫正在巡逻
守卫正在巡逻
守卫正在巡逻
发现敌人!
守卫正在攻击敌人
守卫正在攻击敌人
守卫受伤!
守卫生命值过低,正在逃跑
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('hasEnemy', false)
.defineBlackboardVariable('patrolPoint', 0)

黑板用于在节点之间共享数据。这里定义了三个变量:

  • health: 守卫的生命值
  • hasEnemy: 是否发现敌人
  • patrolPoint: 当前巡逻点编号
.selector('RootSelector')
// 分支1
// 分支2
// 分支3
.end()

选择器按顺序尝试执行子节点,直到某个子节点返回成功。类似于编程中的 if-else if-else

.blackboardExists('hasEnemy') // 检查变量是否存在
.blackboardCompare('health', 30, 'greater') // 比较变量值

条件节点用于检查黑板变量的值。

.log('守卫正在攻击敌人') // 输出日志
.wait(2.0) // 等待2秒
.modifyBlackboardValue('patrolPoint', 'add', 1) // 修改黑板值

动作节点执行具体的操作。

const runtime = guardEntity.getComponent(BehaviorTreeRuntimeComponent);
runtime?.setBlackboardValue('hasEnemy', true);
runtime?.getBlackboardValue('health');

通过BehaviorTreeRuntimeComponent访问和修改黑板变量。

行为树的每个节点返回以下状态之一:

  • Success: 任务成功完成
  • Failure: 任务执行失败
  • Running: 任务正在执行,需要在后续帧继续
  • Invalid: 无效状态(未初始化或已重置)
  • sequence() - 序列节点,按顺序执行所有子节点
  • selector() - 选择器节点,按顺序尝试子节点直到成功
  • parallel() - 并行节点,同时执行多个子节点
  • parallelSelector() - 并行选择器
  • randomSequence() - 随机序列
  • randomSelector() - 随机选择器
  • inverter() - 反转子节点结果
  • repeater(count) - 重复执行子节点
  • alwaysSucceed() - 总是返回成功
  • alwaysFail() - 总是返回失败
  • untilSuccess() - 重复直到成功
  • untilFail() - 重复直到失败
  • conditional(key, value, operator) - 条件装饰器
  • cooldown(time) - 冷却装饰器
  • timeout(time) - 超时装饰器
  • wait(duration) - 等待指定时间
  • log(message) - 输出日志
  • setBlackboardValue(key, value) - 设置黑板值
  • modifyBlackboardValue(key, operation, value) - 修改黑板值
  • executeAction(actionName) - 执行自定义动作
  • blackboardExists(key) - 检查变量是否存在
  • blackboardCompare(key, value, operator) - 比较黑板值
  • randomProbability(probability) - 随机概率
  • executeCondition(conditionName) - 执行自定义条件
BehaviorTreeStarter.start(entity, treeData);
BehaviorTreeStarter.stop(entity);
BehaviorTreeStarter.pause(entity);
// ... 一段时间后
BehaviorTreeStarter.resume(entity);
BehaviorTreeStarter.restart(entity);

现在你已经创建了第一个行为树,接下来可以:

  1. 学习核心概念深入理解行为树原理
  2. 学习资产管理了解如何加载和复用行为树、使用子树
  3. 查看自定义节点执行器学习如何创建自定义节点
  4. 根据你的场景查看集成教程:Cocos CreatorNode.js
  5. 查看高级用法了解更多功能

确保:

  1. 已经安装了 BehaviorTreePlugin
  2. 调用了 plugin.setupScene(scene)
  3. 调用了 BehaviorTreeStarter.start(entity, treeData)
  4. 在游戏循环中调用了 Core.update(deltaTime)
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// 读取
const health = runtime?.getBlackboardValue('health');
// 写入
runtime?.setBlackboardValue('health', 50);
// 获取所有变量
const allVars = runtime?.getAllBlackboardVariables();

使用日志节点:

.log('到达这个节点', 'DebugLog')

或者在代码中监控黑板:

const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));

内置的executeActionexecuteCondition节点只是占位符。要实现真正的自定义逻辑,你需要创建自定义执行器:

参见自定义节点执行器学习如何创建。