当前位置: 首页 > 工具软件 > Behaviac > 使用案例 >

基于behaviac定制行为树编辑器的尝试

麻阳
2023-12-01

基于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(&quot;get_rival&quot;,2500)" />
    # <property ResultOption="BT_INVALID" />
    # or
    # <property ReferenceBehavior="const string &quot;d_patrol&quot;" />
    # <property Task="Self.behaviac::Agent::d_patrol(1200,500)" />
    # or
    # <property Method="Self.behaviac::Agent::patrol_to(&quot;born_pos&quot;,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()
 类似资料: