开源项目
bit-behaviortree:写游戏 AI 别再堆 if-else,用行为树组装逻辑
零依赖的 TypeScript 行为树库,内置选择、序列、装饰等节点类型和三层黑板数据隔离,用组装的方式构建可维护的游戏 AI 逻辑。
bit-behaviortree:写游戏 AI 别再堆 if-else,用行为树组装逻辑
它解决什么问题
怪物 AI 的逻辑最初可能就是”看到玩家就追,追上了就打”,两三个 if-else 就写完了。但需求总是越加越多:追击过程中要躲避障碍、血量低于 30% 要撤退、撤退中被拦截又要反击……每加一条新规则,就要在已经很长的 if-else 链里找地方插进去,改到后面没人敢再碰这段代码,改一个分支可能牵连到看似无关的另一个分支。
行为树把 AI 逻辑拆成一个个可复用的节点(”追击”、”撤退”、”检查血量”),用组合节点(选择、序列、并行)把它们拼装成树。改需求变成”调整树的结构或者加个新节点”,而不是在一段线性代码里插队。bit-behaviortree 是这套模式的零依赖 TypeScript 实现。
安装
npm install @gongxh/bit-behaviortree
核心概念
树的每次 tick 都会返回三种状态之一:
SUCCESS—— 这个节点做完了,成功FAILURE—— 做不了,失败RUNNING—— 还在做,下一帧继续
组合节点:怎么串起子节点
最常用的两个是 Selector 和 Sequence:
import { Selector, Sequence } from '@gongxh/bit-behaviortree';
// Selector:依次尝试子节点,只要有一个成功就整体成功("打不过就跑"的逻辑常用这个)
const combat = new Selector([attackNode, fleeNode]);
// Sequence:依次执行子节点,全部成功才算成功,中途失败就整体失败("先靠近再攻击"这种有先后依赖的逻辑)
const chaseAndAttack = new Sequence([moveToTargetNode, attackNode]);
还有 Parallel(逻辑上并行,实际按顺序执行完所有子节点)、RandomSelector(随机选一个执行,做行为多样性)、ParallelAnySuccess(任一子节点成功就整体成功)。
装饰节点:给子节点加条件或限制
import { LimitTime, Inverter, RepeatUntilSuccess } from '@gongxh/bit-behaviortree';
const timedAttack = new LimitTime(attackNode, 5); // 超过 5 秒还没成功,判定为失败
const notFleeing = new Inverter(fleeNode); // 反转结果:撤退成功变成"失败",常用来做反向条件
const keepTryingToOpen = new RepeatUntilSuccess(openDoorNode, 3); // 最多重试 3 次
叶子节点:真正干活的地方
import { LeafNode, Status } from '@gongxh/bit-behaviortree';
class AttackNode extends LeafNode {
tick(dt: number): Status {
if (!target.isInRange()) return Status.FAILURE;
target.takeDamage(damage);
return Status.SUCCESS;
}
}
黑板系统:三层数据隔离,别把所有东西都塞进全局
行为树的节点之间需要共享数据(比如”当前目标是谁”),但不是所有数据都该是全局可见的。黑板系统分了三层:
- 全局层——所有行为树实例都能看到,适合全局配置、跨树共享的状态
- 树级层——只在这一棵树内可见,随树创建销毁,适合”当前目标”、”任务进度”这类树内共享状态
- 节点层——只在当前节点内可见,适合节点自己的临时变量
// 树级数据:追击目标,整棵树的节点都能读到
this.setRoot('target', enemy);
const target = this.getRoot<Enemy>('target');
// 全局数据:比如游戏难度配置,所有怪物的行为树都读同一份
globalBlackboard.set('difficulty', 2);
分层的意义在于避免数据污染——如果所有数据都塞进全局黑板,A 怪物的行为树写坏了一个变量名,可能悄悄影响到 B 怪物的判断逻辑,这种跨树的数据污染排查起来非常麻烦。
避坑提醒
Selector和Sequence的语义容易搞反:Selector是”任一成功即成功”(类似逻辑或),Sequence是”全部成功才成功”(类似逻辑与),命名和常见编程语言里的 if/switch 直觉不完全一致,团队里最好统一一下理解。- 树内共享的状态优先用树级黑板(
setRoot/getRoot),别图方便都往全局黑板里塞,多个怪物实例共用同一份全局数据的话,字段名冲突或者互相覆盖的问题很难在测试阶段暴露出来,容易线上才发现。 RUNNING状态的节点下一帧会从上次的状态继续,不是重新从头执行——写自定义叶子节点时如果需要”进行中”的行为(比如移动到某个点,没到目标点之前一直返回 RUNNING),要自己维护好这个跨帧的进度状态。
项目信息
- GitHub: https://github.com/gongxh0901/bit-framework/tree/main/bit-behaviortree
- npm: @gongxh/bit-behaviortree
- 可视化编辑器: Cocos Store(付费,支持拖拽式设计行为树)
- 许可证: MIT License
零依赖,纯 TypeScript 实现,不限定 Cocos Creator,机器人控制、自动化脚本这类需要组织复杂决策逻辑的场景也能用。