行为树由一个个节点组成
- 结构:树状结构
- 运行流程:从根节点开始自顶向下往下遍历,每经过一个节点就执行节点对应的功能。
我们规定,每个节点都提供自己的excute函数,返还执行失败/成功结果。
然后根据不同节点的执行结果,遍历的路径随之改变,而这个过程中遍历到什么节点就执行excute函数。
主流的行为树实现,将节点主要分为四种类型。
下面列举四种节点类型及其对应excute函数的行为:
- 控制节点(非叶节点),用于控制如何执行子节点(控制遍历路径的走向)。
- 条件节点(叶节点),行为是提供条件的判断结果。
- 行为节点(叶节点):
行为节点是代表智能体行为的叶节点,其执行函数一般位该节点代表的行为。
行为节点的类型是比较多的,毕竟一个智能体的行为是多种多样的,而且都得根据自己的智能体模型定制行为节点类型。
这里列举一些行为:站立,射击,移动,跟随,远离,保持距离.... - 装饰节点 :行为是修饰(辅助)其他三类节点。例如执行结果取反/并/或,重复执行若干次等辅助修饰节点的作用,均可做成装饰节点。
持续行为
一些行为是可以瞬间执行完的(例如转身?),
而另外一些动作则是执行持续一段时间才能完成的(例如攻击从启动攻击行为到攻击结算要1秒左右的时间)。
因此,这些持续行为节点的excute函数里,应先启动智能体的持续行为,然后挂起该行为树(更通俗地说是暂停行为树),等到持续时间结束才允许退出excute函数并继续遍历该行为树。
为了支持挂起行为树而不影响其他CPU代码执行,我们往往需要利用协程等待该其行为完成而不产生CPU阻塞,而且开销远低于真正的线程。
此外,一般是一个行为树对应维护一个协程。
条件节点:
执行节点不会总是一帆风顺的,有成功也总会有失败的结果。
这就是引入前提条件的作用——
满足前提条件,才能成功执行行为,返还执行成功结果。否则不能执行行为,返还执行失败结果。
但是每个节点的前提总会不同,或有些没有前提(换句话说总是能满足前提)。
一个可行的做法是:让行为节点含有bool函数对象(或函数接口)。这样对于不同的逻辑条件,就可以写成不同的bool函数,绑定给相应的行为节点。
现在比较成熟的做法是把前提条件抽象分离成新的节点类型,称之为条件节点。
将其作为叶节点混入行为树,提供条件的判断结果,交给控制节点决策。
它相当模块化,更加方便适用。
这里的Sequence节点是上面控制节点的一种:能够让其所有子节点依次运行,若运行到其中一个子节点失败则不继续往下运行。
这样可以实现出不满足条件则失败的效果。
相比较传统的有限状态机:
- 易脚本化/可视化的决策逻辑
- 逻辑和实现的低耦合,可复用的节点
- 可以迅速而便捷的组织较复杂的行为决策
这里并不是说有限状态机一无所用:
- 状态机可以搭配行为树:状态机负责智能体的身体状态,行为树则负责智能体的智能决策。这样在行为树做决策前,得考虑状态机的状态。
- 状态机适用于简单的AI:对于区区需两三个状态的智能,状态机解决绰绰有余。
- 状态机运行效率略高于行为树:因为状态机的运行总是在当前状态开始,而行为树的运行总在根开始,这样就额外多了一些要遍历的节点(也就多了一些运行开销)。
在《杀手:赦免》的人群系统里,人群的状态机AI只有简单的3种状态,由于人群的智能体数量较多,若采取行为树AI,则会大大影响性能。
代码示例:
enum Status {
SUCCESS,
FAILURE,
RUNNING
}
// 基础节点类
abstract class Node {
abstract execute(): Status;
}
// 非叶节点类,用于容纳子节点
abstract class CompositeNode extends Node {
protected children: Node[] = [];
addChild(child: Node): void {
this.children.push(child);
}
}
// 选择器节点:依次执行子节点,直到其中一个成功为止
class SelectorNode extends CompositeNode {
execute(): Status {
for (const child of this.children) {
const status = child.execute();
if (status === Status.SUCCESS) {
return Status.SUCCESS;
}
}
return Status.FAILURE;
}
}
// 序列节点:依次执行子节点,直到其中一个失败为止
class SequenceNode extends CompositeNode {
execute(): Status {
for (const child of this.children) {
const status = child.execute();
if (status === Status.FAILURE) {
return Status.FAILURE;
}
}
return Status.SUCCESS;
}
}
// 条件节点:判断某个条件是否满足,并返回结果
abstract class ConditionNode extends Node {
abstract check(): boolean;
execute(): Status {
return this.check() ? Status.SUCCESS : Status.FAILURE;
}
}
// 行为节点:执行具体的动作,并返回结果
abstract class ActionNode extends Node {
abstract performAction(): boolean;
execute(): Status {
return this.performAction() ? Status.SUCCESS : Status.FAILURE;
}
}
// 装饰节点:用于修改其他节点的行为,通常只有一个子节点
abstract class DecoratorNode extends Node {
protected child: Node;
constructor(child: Node) {
super();
this.child = child;
}
}
// 反转节点:将子节点的结果反转
class InverterNode extends DecoratorNode {
execute(): Status {
const status = this.child.execute();
if (status === Status.SUCCESS) {
return Status.FAILURE;
} else if (status === Status.FAILURE) {
return Status.SUCCESS;
} else {
return status;
}
}
}
// 重复执行装饰节点:重复执行子节点指定的次数,或直到满足某个条件
class RepeaterNode extends DecoratorNode {
private maxRepeats: number;
private stopOnSuccess: boolean;
private stopOnFailure: boolean;
constructor(child: Node, maxRepeats: number, stopOnSuccess: boolean = false, stopOnFailure: boolean = false) {
super(child);
this.maxRepeats = maxRepeats;
this.stopOnSuccess = stopOnSuccess;
this.stopOnFailure = stopOnFailure;
}
execute(): Status {
let count = 0;
while (this.maxRepeats === -1 || count < this.maxRepeats) {
const status = this.child.execute();
if (this.stopOnSuccess && status === Status.SUCCESS) {
return Status.SUCCESS;
}
if (this.stopOnFailure && status === Status.FAILURE) {
return Status.FAILURE;
}
count++;
}
return Status.SUCCESS;
}
}
// 判断敌人是否在范围内的条件节点
class IsEnemyInRange extends ConditionNode {
check(): boolean {
// 判断敌人是否在范围内
return Math.random() < 0.5; // 50% 概率敌人在范围内
}
}
// 追击敌人的行为节点
class ChaseEnemy extends ActionNode {
performAction(): boolean {
console.log("Chasing the enemy!");
return true; // 假设追击成功
}
}
// 攻击敌人的行为节点
class AttackEnemy extends ActionNode {
performAction(): boolean {
console.log("Attacking the enemy!");
return true; // 假设攻击成功
}
}
// 创建行为树
const root = new SelectorNode();
const sequence = new SequenceNode();
root.addChild(sequence);
const condition = new IsEnemyInRange();
sequence.addChild(condition);
const chase = new ChaseEnemy();
const repeater = new RepeaterNode(chase, 3); // 重复执行ChaseEnemy 3次
sequence.addChild(repeater);
const attack = new AttackEnemy();
sequence.addChild(attack);
// 执行行为树
const status = root.execute();
console.log(`Behavior tree execution status: ${Status[status]}`);