基于behaviac定制行为树编辑器的尝试
编辑器提供可视化操作窗口,操作方便,容易理解。
为什么选择behaviac
*理论上,游戏开发前后端共用编辑器,对于使用者(开发人员、策划)来说,应该是最方便的,unity前端开发一般使用Behavior Designer(以下简称BD)插件编辑行为树,但是官方的BD,只为unity定制,不能导出xml、C++等数据格式,而behaviac可以满足这个需求,对于erlang游戏服务端,可以解释xml数据,变为erlang的数据格式;
*相对于xlsx配表配置行为树,behaviac编辑器有利有弊,behaviac提供可视化窗口,不容易出错,但它的使用需要一个熟悉的过程,各项目需要根据实际情况选择使用;
behaviac操作要点
-behaviac提供选择、序列、子树等丰富的节点,直接在编辑器拖拽即可添加;
-每个节点支持配置私有参数;
-支持树嵌套树,直接把一棵树拖拽到另一棵树即可;
-支持向子树传递参数(行为树局部变量),实现不同的子树调用,参数不一样,详见:
http://www.behaviac.com/language/zh/task/
-支持添加自定义元信息(视图-->元信息浏览):
[元信息添加](https://img-blog.csdn.net/20161114171043775)
-行为树编辑器的使用需要了解行为树的基础,注意不要编辑死循环的行为树,一般编辑行为树由开发人员主导,策划很多时候,只需要修改参数;
behaviac使用例子
-编辑行为树:
![调用了子树的行为树](https://img-blog.csdn.net/20161114172807971)
-被调用的子树:
![子树d_attack](https://img-blog.csdn.net/20161114173210783)
![子树d_back_psv](https://img-blog.csdn.net/20161114173244787)
![子树d_patrol](https://img-blog.csdn.net/20161114173315818)
-解释xml数据,导出erlang数据的脚本:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Date : 2016-11-09 19:57:10
# Author : zwq (wesonzhang@gmail.com)
# Description:
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
import io
import xlsx_lib
import math
from export_erl import *
class BehaviorTree(object):
def __init__(self, filenames):
super(BehaviorTree, self).__init__()
self.filenames = filenames
self.ai_name = dict() # ai名称,用于去重
self.data = list() # 保存最后要导出的数据
def get_xml_data(self):
for filename in self.filenames:
level = 1 #节点的深度从1开始
root = ET.parse(filename).getroot()
self.walk_data(root, root, level)
#遍历所有的节点
def walk_data(self, root, node, level):
rec_dic = dict()
child_nodes = node.getchildren()
if node.tag == 'behavior':
pass
elif node.tag == 'node' and node.attrib['class'] == 'Selector':
rec_dic["type"] = 'sel'
rec_dic["name"] = root.attrib["name"]
#遍历每个子节点
rec_dic["children"] = self.make_children(child_nodes)
elif node.tag == 'node' and node.attrib['class'] == 'Sequence':
rec_dic["type"] = 'seq'
rec_dic["name"] = root.attrib["name"]
rec_dic["children"] = self.make_children(child_nodes)
if len(rec_dic) > 0 and self.ai_name.has_key(rec_dic["name"]) == False:
self.data.append(rec_dic)
self.ai_name[rec_dic["name"]] = True
if len(child_nodes) == 0:
return
for child in child_nodes:
self.walk_data(root, child, level + 1)
return
def make_children(self, child_nodes):
childs = list()
for child in child_nodes:
rec_dic = dict()
child_nodes = child.getchildren()
if child.tag == 'node' and child.attrib['class'] == 'Action':
rec_dic["type"] = 'act'
name, children = self.mk_name_child_for_act(child_nodes)
rec_dic["name"] = name
rec_dic["children"] = '[]'
if self.ai_name.has_key(rec_dic["name"]) == False:
self.data.append(rec_dic)
self.ai_name[rec_dic["name"]] = True
childs.append(children)
elif child.tag == 'node' and child.attrib['class'] == 'Condition':
rec_dic["type"] = 'con'
name, children = self.mk_name_child_for_con(child_nodes)
rec_dic["name"] = name
rec_dic["children"] = '[]'
if self.ai_name.has_key(rec_dic["name"]) == False:
self.data.append(rec_dic)
self.ai_name[rec_dic["name"]] = True
childs.append(children)
elif child.tag == 'node' and child.attrib['class'] == 'ReferencedBehavior':
name, children = self.mk_name_child_for_act(child_nodes)
childs.append(children)
return "[" + ", ".join(childs) + "]"
# <property Method="Self.behaviac::Agent::assign_target("get_rival",2500)" />
# <property ResultOption="BT_INVALID" />
# or
# <property ReferenceBehavior="const string "d_patrol"" />
# <property Task="Self.behaviac::Agent::d_patrol(1200,500)" />
# or
# <property Method="Self.behaviac::Agent::patrol_to("born_pos",uint Self.behaviac::Agent::_$local_task_param_$_1)" />
def mk_name_child_for_act(self, child_nodes):
for child in child_nodes:
if child.tag == 'property':
res = child.attrib.get('Method', 'undef')
if res == 'undef':
res = child.attrib.get('Task', 'undef')
if res == 'undef':
continue
if res.find('local_task_param') == -1:
strlist = res.split('::')
name_para = self.filter(strlist)
[name, para] = name_para.split('(')
para = '[' + para
para = para.replace(')', ']')
if para == '[]':
return name, name
else:
return name, '{' + name + ',' + para + '}'
else:
strlist = res.split('(')
name = self.filter(strlist[0].split('::'))
paras = strlist[1].split(',')
ps = '['
length = len(paras)
for i in range(length):
if paras[i].find('local_task_param') == -1:
ps = ps + self.filt_quot(paras[i])
else:
index = paras[i].split('::')[-1].split('_')[-1]
index = index.replace(')', '')
index = '#' + str(int(index) + 1)
index = '\'' + index + '\''
ps = ps + index
if i < length - 1:
ps = ps + ','
ps = ps + ']'
return name, '{' + name + ',' + ps + '}'
# <property Operator="GreaterEqual" />
# <property Opl="uint Self.behaviac::Agent::target_out_range" />
# <property Opr="uint Self.behaviac::Agent::_$local_task_param_$_1" />
def mk_name_child_for_con(self, child_nodes):
name = ''
paras = '['
for child in child_nodes:
if child.tag == 'property':
res = child.attrib.get('Operator', 'undef')
if res == 'GreaterEqual':
paras = paras + '\'>=\''
continue
elif res == 'Greater':
paras = paras + '\'>\''
continue
elif res == 'LessEqual':
paras = paras + '\'=<\''
continue
elif res == 'Less':
paras = paras + '\'<\''
continue
elif res == 'Equal':
paras = paras + '\'=:=\''
continue
if res == 'undef':
res = child.attrib.get('Opl', 'undef')
if res != 'undef':
strlist = res.split('::')
name = self.filter(strlist)
continue
else:
res = child.attrib.get('Opr', 'undef')
if res == 'undef':
continue
strlist = res.split('::')
para = self.filter(strlist)
ParaIndex = int(para.split('_')[-1]) + 1
ParaIndex = '#' + str(ParaIndex)
ParaIndex = '\'' + ParaIndex + '\''
paras = paras + ',' + ParaIndex + ']'
return name, '{' + name + ',' + paras + '}'
def filter(self, strlist):
for s in strlist:
if s == 'Self.behaviac':
continue
if s.find('Self.behaviac') != -1:
continue
if s == 'Agent':
continue
return self.filt_quot(s)
def filt_quot(self, s):
return s.replace('"', '').replace('"', '')
def make_xlsx(bt):
xlsx = xlsx_lib.Xlsx()
xlsx.erl_filename = "data_ai.erl"
xlsx.xlsx_filename = bt.filenames[0] + '.xlsx'
xlsx.module = "data_ai"
xlsx.record_name = "data_ai"
xlsx.keys = ["name", "type", "children"]
xlsx.out_erl = set(xlsx.keys)
xlsx._data = bt.data
xlsx.types = {'name':'ATOM','type':'ATOM', 'children':'ATOM'}
return xlsx
def convert(filenames):
BT = BehaviorTree(filenames)
BT.get_xml_data()
x = make_xlsx(BT)
write_to_file(x, '.')
def main():
pass
def bench():
import time
t0 = time.clock()
convert(["ai_default.xml", "d_back_psv.xml", "d_patrol.xml", "d_attack.xml"])
print("use time %s" % (time.clock() - t0))
if __name__ == '__main__':
bench()