行为树采用节点描述行为逻辑,主要有:选择节点、顺序节点、并行节点、修饰节点、随机节点、条件节点、行为节点。一棵行为树表示一个AI逻辑。要执行这个 AI 逻辑,需要从根节点开始遍历整棵树,遍历执行的过程中,父节点根据自身的类型,确定需要如何执行、执行哪些子节点并继续执行,子节点执行完毕后,会将执行结果反馈给父节点。
从结构上节点可分为:组合节点、叶节点 (单节点)。组合节点就是拥有子节点的节点。叶节点 (单节点)就是没有子节点的节点。
组合节点:选择节点、顺序节点、并行节点、修饰节点、随机节点。
叶节点 :条件节点、行为节点。
如下图一个行为树示例图:
行为树节点顺序如下,
节点1 为根节点, 节点2、节点5为中间节点,节点3、4、6、7,为子节点
节点 1、2、5为组合节点,节点3、4、6、7为单节点
节点执行结果
条件执行结果可分为三种:Fail、Success、Running
。
Fail
: 节点执行失败(如:条件节点判定为 false、执行节点失败、没有符合执行的节点等)
Success
:节点执行成功(如:条件节点判定为 true、执行节点成功等)
Running
:节点执行中(如:正在跑向目标、正在吃饭、动画播放中等)
行为树节点
叶节点
组合节点
组合节点用来控制树的遍历方式,每种组合节点的遍历方式都不相同。
1.选择节点
选择节点(Select),遍历方式为从左到右依次执行所有子节点,只要节点返回 Fail,就继续执行后续节点,直到一个节点返回Success或Running为止,停止执行后续节点。如果有一个节点返回Success或Running则向父节点返回Success或Running。否则向父节点返回 Fail。
注意:当子节点返回 Running时,除了停止实行后续节点、向父节点返回 Running 外,还要保存返回Running 的这个节点,下次迭代则直接从该节点开始执行。
如果选择节点有记录正在 Running的节点,则遍历时就要从上次记录的Running节点开始,而不是从最左边第一个节点开始执行。其他逻辑不变
选择节点伪代码如下:
index = 1
if != lastRunningNode null then
index = lastRunningNode.index
end
lastRunningNode = null
for i <- index to N do
Node node = GetNode(i);
result = node.execute()
if result == fail then
continue;
end
if result == success then
return success
end
if result == running then
lastRunningNode = node
return running
end
end
return fail
2.顺序节点
顺序节点(Sequence),它从左向右依次执行所有节点,只要节点返回Success,就继续执行后续节点,当一个节点返回Fail或 Running 时,停止执行后续节点。向父节点返回 Fail 或 Running,只有当所有节点都返回 Success 时,才向父节点返回 Success。
与选择节点相似,当节点返回Running 时,顺序节点除了终止后续节点的执行,还要记录返回 Running的这个节点,下次迭代会直接从该节点开始执行。
顺序节点伪代码如下:
index = 1
if != lastRunningNode null then
index = lastRunningNode.index
end
lastRunningNode = null
for i <- index to N do
Node node = GetNode(i);
result = node.execute()
if result == fail then
return fail;
end
if result == running then
lastRunningNode = node
return running
end
end
return success
3.随机选择节点
随机节点(Random)遍历优先级与选择节点、顺序节点不同。
选择节点、顺序节点都是默认优先级的,最左边的节点具有最高优先级,最右边的优先级最低。随机节点则是随机执行每个子节点。如从左到右顺序为:A、B、C、D。而执行时可能是D、A、C、B 或 C、B、D、A 等。
当一个节点返回 Success 或者 Running 时,则停止执行后续节点,向父节点返回 Success或Running。当返回 Running 时记录返回 Running 的节点,下次迭代时首先执行 Running 节点。比如做饭时,根据AI心情不同每天随机选择吃 宫保鸡丁、香菇肉片、还是鱼香肉丝。这样提高游戏结果的多样性。
随机节点伪代码:
Random 一个随机数组
index = 1
if != lastRunningNode null then
index = lastRunningNode.index
将 index 添加到随机数组的第一位
end
lastRunningNode = null
for i <- 1 to N do
Node node = GetNode(i);
result = node.execute()
if result == fail then
continue;
end
if result == success then
return result
end
if result == running then
lastRunningNode = node
return running
end
end
return fail
4.修饰节点
修饰节点(Decorator)修饰节点不能独立存在,其作用为对子节点进行修饰,以得到我们所希望的结果.
修饰节点有很多种,其中有一些是用于决定是否允许子节点运行的,也叫过滤器,例如 Until Success, Until Fail 等,首先确定需要的结果,循环执行子节点,直到节点返回的结果和需要的结果相同时向父节点返回需要的结果,否则返回 Running。
如需要结果为 Until Fail,则当子节点返回 Success或者 Running 时都向父节点返回 Running,当节点返回结果为 Fail 时,才向父节点返回 Fail。
反之需要的结果为 Until Success,则当子节点返回 Fail 或者 Running 时向父节点返回 Running,当节点返回 Success 时,才向父节点返回 Success。
修饰节点常用的几个类型如下:
Inverter
对子节点执行结果取反
Repeater
重复执行子节点 N 次
Return Failure
执行到此节点时返回失败
Return Success
执行到此节点时返回成功
Unitl Failure
直到失败,一直执行子节点
Until Success
直到成功,一直执行子节点
修饰节点 Until Success、Until Fail
伪代码:
defaultResult = 期望结果
do
Node node = GetChild(0)
result = node.Execute();
if result != defaultResult then
return running
end
return defaultResult
end
5.并行节点
并行节点(Parallel)有 N 个节点,每次执行所有节点,直到一个节点返回 Fail 或者全部返回 Success为止,此时并行节点向父节点返回 Fail 或者 Success,并终止执行其他所有节点。否则至少有一个节点处于 Running 状态,则执行完所有节点向父节点返回 Running。与选择节点不同的是并行节点不需要记录返回 Running 结果的节点,每次执行都会从左向右依次执行所有子节点。
当外界环境发生变化时,影响到子节点的执行,如何处理?如,AI 正在炒菜,突然煤气不足,无法点燃,则AI 就不能继续炒菜这个动作了,将退出炒菜的所有节点。
并行节点对于外部环境发生变化,需要随时应对变化的AI 决策十分有效。如,AI 追逐玩家,使用并行节点就特别合适,需要并行的执行 “是否看到玩家?”,“朝玩家移动”。当某一帧无法看到玩家,则不能执行朝向玩家移动这个节点的行为。
并行节点伪代码:
successCount = 0
for i <- index to N do
Node node = GetNode(i);
result = node.execute()
if result == fail then
return fail;
end
if result == success then
++successCount
continue
end
if result == running then
continue
end
end
if successCount >= childCount then
return success
end
return running
到此 行为树 Behavior Tree 中七个常用节点介绍完毕