前言
行为树(Behavior Tree)是一种常用于游戏开发和人工智能领域的算法,相比传统的有限状态机(FSM)和任务队列(BT)等方法,它更加灵活和易于扩展。在前端开发领域,我们也经常需要使用行为树来实现复杂的用户交互逻辑等。
本文将介绍一款使用 TypeScript 实现的行为树框架 behavior3ts,通过实例演示其使用方法,帮助读者掌握行为树的编写和使用技巧。
安装与导入
behavior3ts 可以通过 npm 包管理工具来进行安装,只需在项目根目录中运行如下命令即可:
npm install behavior3ts
然后,在 TypeScript 代码中,我们需要通过引入 BehaviorTree、Composite、Decorator 等相关模块,来使用 behavior3ts 提供的各种类和方法:
import { BehaviorTree, Blackboard, Composite, Decorator, Action, Condition } from 'behavior3ts';
行为树结构
行为树是由节点(Node)和连接线(Edge)组成的树形结构,其中每个节点代表一种行为或决策,每个连接线则代表行为之间的联系和逻辑顺序。行为树的结构一般分为三种类型:组合节点(Composite)、装饰节点(Decorator)和行为节点(Action/Condition)。
组合节点
组合节点是行为树中的容器节点,可以组合多个子节点,形成一个完整的行为序列。组合节点主要分为如下几类:
- Sequence
- Priority
- Parallel
- RandomSequence
- RandomPriority
- MemSequence
- MemPriority
以 Sequence 为例,它将所有子节点按照顺序执行,只有当所有子节点都返回 success 才返回 success,否则返回 failure。实现这个结构,我们可以这样编码:
-- -------------------- ---- ------- -- -------- ----- ------------ - --- ---------------- ------------------ ----- ---------- - --- ------------- -- ------- ----- -------------- ------- --------- - ------------- - -------- ------------- - - --- ------------------------------- ----- --------- --- -- ---- - --- ------------------------------- ----- --------- --- -- ---- - --- ------------------------------- ----- --------- --- -- ---- - -- - -- -------- ------ ------------- --------------------- - ------ - ----------- ---------- -- - - -- --------- --------- --------------------- ------- ------------- - ----------- ----------- - -- ------------ ------ --- -------- ----- ------------- -------- --- ----- -------- ------- ---------------------- ------- ------------- - ------ ------------------- ------- - ----- ------ -- - ------------------- - --------- ----------------- --- ----- - --------------- ----------- --------------- ------ -------------- - -
组合节点的作用相当于编程语言中的分支结构,可以按照一定的规则判断子节点的执行顺序和停止条件。在 behavior3ts 中,组合节点还提供了多种预设的混合类型,可以更好地适配各种行为树需求。
装饰节点
装饰节点是行为树中的修饰性节点,可以用来修改或限制节点的执行情况,例如执行次数、执行时间、优先级等。装饰节点的常用类型有以下几种:
- Repeater
- MaxTime
- Inverter
- Succeeder
- Failer
- Error
- UntilSuccess
- UntilFailure
以 Repeater 为例,它将子节点无限次执行,直到某个子节点返回 failure 或达到指定执行次数为止。实现这个结构,我们可以这样编码:
-- -------------------- ---- ------- -- -------- ----- ------------ - --- ---------------- ------------------ ----- ---------- - --- ------------- -- ------- ----- -------------- ------- -------------------------------- - ------------- - -------- ---------- - --- ------------------------------- ----- -------- --- -- ---- -------------- - --- --------------------------------- -------- - --- -- ---- - -- -------- ------ ------------- --------------------- - ------ - ----------- ---------- -- - - -- --------- --------- --------------------- ------- ------------- - ----------- ----------- - -- ------------ ------ --- -------- ----- ------------- -------- --- ----- -------- ------- ---------------------- ------- ------------- - ------ ------------------- ------- - ----- ------ -- - ------------------- - --------- ----------------- --- ----- - --------------- ----------- --------------- ------ -------------- - -
装饰节点的作用相当于编程语言中的函数或方法,可以对子节点进行递归或循环调用,并在适当的时候返回或传递数据。
行为节点
行为节点是行为树中的基本执行单元,用来实现具体的行为操作或状态判断。行为节点的类型一般分为 Action(直接执行某项操作)和 Condition(判断某项条件)两种。例如:
-- -------------------- ---- ------- -- ------------ ----- -------- ------- --------------------------- - ------------------- ------- - ----- ------ -- - ------------------- - --------- ----------------- ---------------- - --------------- ----------- --------------- ------ -------------- - - -- ------------ ----- ----------- ------- ------------------------------ - ------------------- ------- - ----- ------ -- - ------------------- - --------- ----------------------- ---------------- - ------ ----- - - -- ----- --------- --------------- ------- ------------- - -- --- - -- -------- ----- -------- --------------- - - ----------- ---------- --
实际上,以上三种节点的区别并不是非常明显,它们的代码实现都很相似,都需要重写父类的 doAction 或 checkCondition 方法,并在其中进行具体的执行或判断操作。
实例演示
下面,我们通过一个实际的场景来演示 behavior3ts 的使用方法。该场景是一个玩具汽车遥控器的示例,用户可以通过控制器上的四个方向按钮,控制玩具车沿同一方向前进或后退。具体来说,该场景需要实现如下功能:
- 当用户按下前进或后退按钮时,玩具车会向前或向后移动一定距离,然后停止。
- 当用户再次按下同一方向的按钮时,玩具车会再次移动,直到再次按下另一方向的按钮或到达终点位置。
- 当用户按下相反方向的按钮时,玩具车会停止前一种移动方式,立即转向并开始相反方向的移动。
按照上述需求,我们可以设计如下的行为树结构:
Sequence(顺序节点) └─ Selector(选择节点) ├─ Repeater(循环节点) │ └─ Sequence(顺序节点) │ ├─ Move(行为节点) │ └─ Wait(行为节点) └─ Action(行为节点)
其中,Sequence 表示按一定的顺序执行两个子节点,Selector 表示按优先级执行两个子节点中的一项,Repeater 表示循环执行一个子节点,直到达到指定次数或条件不满足为止,Move 表示控制玩具车向前或向后移动一定距离,Wait 表示等待一段时间,Action 表示停止玩具车。
在代码中,我们可以这样实现这个行为树:
-- -------------------- ---- ------- ------ - ------------- ----------- ---------- ---------- ------- ----- - ---- -------------- -- -------- ----- ------------ - --- ---------------- ------------------ ----- ---------- - --- ------------- -- ------- ----- -------------- ------- -------------------------------- - ------------- - -------- ------------- - - -- ---- --- --------------------------------- --------- - -- ---- --- --------------------------------- --------- - -- ---- --- --------------------------------- ------ -- -- ----- -- - -------- - -- ------ -- --- -- ---- --- ------------------------------- ----- ------ --- -- --- -- ---- --- ------------------------------- ----- --------- --- -- --- -- - -- -------- ------ ------------- --------------------- - ------ - ----------- ---------- -- - - -- --------- --------- --------------------- ------- ------------- - ----------- ----------- -- ----------- ------ ----- --- ---------- ------- -- ------- -- ------- -- ------- -------- ------- - -- -------- ----- ---- ------- ----------------------------- - --------- ----------------- ---------------------- - --------------- ----------- --------------- --------- -- ----------------- - --- ------ -------------- - - ----- ---- ------- ----------------------------- - --------- ----------------- ---------------------- - --------------- ----------- --------------- ------ -------------- - - ----- ---- ------- ----------------------------- - --------- ----------------- ---------------------- - --------------- ----------- --------------- ----------------- - -- ------ -------------- - - ----- ------- ------- ----------------------------- - --------- ----------------- ---------------------- - --------------- ----------- --------------- ----------------- - -- --------- - -- --------------- - -- ------ -------------- - - -- ------------- --------------------------- -- --- ------------------- -- --- ------------------------- ---- --- -- ----- --------------------
在上面的代码中,我们首先定义了一个行为树类 MyBehaviorTree,该类继承自 Composite 类,表示组合节点顺序执行两个子节点。其中,第一个子节点是一个 Selector 选择节点,表示按照一定的优先级执行两个子节点中的一个。具体来说,该选择节点包含了两个子节点:一个循环节点 Repeater,一个行为节点 Action。
循环节点 Repeater 表示按照一定的次数(如 3 次)执行一个子节点,该子节点在具体实现中,会判断当前行动方向(direction)并执行前进或后退操作。该循环节点包含了一个 Sequence 顺序节点,用来按照一定的顺序执行两个子节点:行动(Move)和等待(Wait)。行动节点 Move 的作用是让玩具车向当前行动方向前进或后退一定距离(如 10px),等待节点 Wait 则是停顿一段时间(如 1s),以模拟玩具车到达目的地的效果。
行为节点 Action 则表示停止当前的移动操作,该节点实现中,会将当前行动方向(direction)置为 0,从而停止所有的移动操作。
最后,我们在代码中通过 blackboard 对象来设置行为树的上下文信息,包括当前行动方向(direction)、玩具车当前位置(x)和目标位置(targetX)。在行为树运行时,首先会执行 MyBehaviorTree 类的构造函数,生成一条完整的行为树,然后调用 Composite.tick() 方法来执行该树,并根据子节点的执行结果,返回 success 或 failure。
总结
通过以上代码实例,我们可以看到,行为树在前端开发领域中有着广泛的应用场景和实现方法。无论是游戏编程、人工智能还是用户交互等领域,行为树都可以提供一种简洁高效的算法实现,优化程序运行效率,提高代码的可读性和可维护性。
另外,使用 behavior3ts 框架可以大大简化行为树的实现和调试过程,提高开发效率,减少出错的可能性。虽然 behavior3ts 的学习门槛较高,但一旦掌握了其中的设计思想和使用技巧,就可以轻松实现各种复杂的行为树结构和逻辑,为项目的开发和部署提供了强有力的支持。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/600572c881e8991b448e8f29