行为树是一种在游戏中常用的实现AI的方式,通过行为树可行图形化实现常用的程序结构。
每次执行AI时,从根节点(root)或running节点开始遍历,父节点执行子节点,子节点执行完后将结果返回父节点,父节点根据子节点的结果来决定接下来怎么做。行为树的执行过程就是查找要执行的叶子节点的过程,查找的依据就是先执行的叶子节点的结果。通常每个节点有三个状态:成功(success),失败(failure),执行中(running)。行为树的遍历实际上是一次函数调用,立即完成的,但是动作节点的执行确实一个过程,不能立即完成。这就导致动作节点在为完成之前会被多次执行,因此引入了running状态,用于表示节点正在执行,每次执行时,跳过之前已经执行完毕的节点,从上次未执行完毕的节点开始执行。但同时需要注意的是,running状态下AI不会遍历行为树,不能影响环境的变化,running节点必须是一个不可打断动作节点。
组合节点(Composite),从左往右依次执行,类似逻辑与。
1.子节点返回failure,则停止执行,返回failure,下次从根节点开始执行;
2.子节点返回running,则停止执行,返回running,下次从running节点开始执行;
3.子节点返回success,则执行下一个节点,所有节点都返回success,则返回success,下次从根节点开始执行。
其伪代码流程如下:
def sequence_node(owner, node_name):
run_index = get_run_index(node_name)
while run_index < len(child_list):
res = child_list[run_index](owner)
if res == FAILURE:
set_run_index(node_name, 0)
return res
elif res == RUNNING:
set_run_index(node_name, run_index)
return res
else:
run_index += 1
set_run_index(node_name, 0)
return SUCCESS
组合节点(Composite),从左往右依次执行,类似逻辑与。
1.子节点返回success,则停止执行,返回success,下次从根节点开始执行;
2.子节点返回running,则停止执行,返回running,下次从running节点开始执行;
3.子节点返回failure,则执行下一个节点,所有节点都返回failure,则返回failure,下次从根节点开始执行。
其伪代码流程如下:
def selector_node(owner, node_name):
run_index = get_run_index(node_name)
while run_index < len(child_list):
res = child_list[run_index](owner)
if res == SUCCESS:
set_run_index(node_name, 0)
elif res == RUNNING:
set_run_index(node_name, run_index)
else:
run_index += 1
set_run_index(node_name, 0)
return FAILURE
组合节点(Composite),会依次执行其全部子节点,然后根据所有子节点的返回值,指定该节点的返回值。
其伪代码流程如下:
def parallel(owner, node_name):
run_index_list = get_run_index(node_name)
if len(run_index_list) > 0:
for index in run_index_list:
state_dic[index] = child_list[index](owner)
else:
run_index = 0
while run_index < len(child_list):
state_dic[run_index] = child_list[run_index](owner)
run_index += 1
if state_dic.count(SUCCESS) >= MIN_SUCCESS:
return SUCCESS
elif state_dic.count(FAILURE) >= MIN_FAILURE:
return FAILURE
else:
return RUNNING
组合节点,根据填写的概率权重,随机执行其一个子节点。
其伪代码流程如下:
def probability(owner, node_name):
run_index = get_run_index(node_name)
if get_exe_state(node_name) == STATE_NO_RUNNING:
weight = random.uniform(0, total_weight)
run_index = 0
for i, w in enumerate(weights):
if weight < w:
run_index = i
break
else:
weight -= w
res = child_list[run_index](owner)
if res == RUNNING:
set_run_index(node_name, run_index)
set_exe_state(node_name, STATE_RUNNING)
else:
set_run_index(node_name, 0)
set_exe_state(node_name, STATE_NO_RUNNING)
return res
中间节点,对子节点的返回值做相应的装饰,因此一定会有一个子节点。
叶子节点(Leaf)
条件成立返回success,失败返回failure。
叶子节点(Leaf)
返回success
组合节点都有行为树的解析器实现,使用者需要自己实现的是叶子节点。
下面使用行为树节来实现一个怪物的AI,来演示各逻辑结构如何实现。
顺序逻辑结构推挤用顺序节点实现,每个子节点都是动作节点。
假设,要实现的AI,先查找目标,然后攻击。
if结构:
假设,要实现的AI,如果发现目标,则进行攻击。
if-else结构:
例如,要实现的AI,如果发现目标,则进行攻击,否则巡逻。
if-elseif结构:
例如,要实现的AI,如果发现目标,则进行攻击,如果发现掉落装备,则捡装备,否则巡逻。
例如,要实现的AI,如果发现目标,则进行攻击,如果发现掉落装备,则捡装备,否则巡逻。
捡装备的过程是,移动到装备所在位置,然后捡装备。
在移动到装备位置的过程中,此节点返回running,直达移动到该点,才返回success。在此过程中,循环的执行该节点,判断是否移动到了指定位置。