您的位置:首页 > 运维架构 > 网站架构

共享节点型的行为树架构(2)

2016-01-08 15:23 531 查看
转自:http://www.aisharing.com/archives/572上次聊到了共享节点型行为树的基本概念和节点组成,简单的来说,这种行为树就是把构成树的结构性数据,和运行时数据分开了,将结构性数据在多个行为树中共享,这样当存在大量的智能体的时候,内存的使用会减少很多。在上次的文章里,提到了行为(Behavior),节点(Node),任务(Task)的概念和实现方法,这里再回顾一下这三个概念的内容节点(Node):和原来的节点概念有所不同,这个节点仅包含结构性数据,在所有的行为树实例中共享。并且负责创建该节点所能执行的任务。比如一个带选择功能的控制节点,就会产生一个带选择逻辑的任务。

任务(Task):保存运行时特有的数据,并执行逻辑

行为(Behaivor):保存由当前节点做创建的任务,并更新任务的状态

有了这样的基本节点,我们就可以将其他的控制节点来实现出来,我还是会实现三种基本的控制节点:选择,序列和并行,由于这三种节点都不是叶节点,并且在Node的实现中,可以看到,我们并没有保留用于保存子节点的成员变量,所以,我们首先需要创建一个带子节点的Node类,称之为组合节点(Composite Node)
1: typedef std::vector<Node*> Nodes;
2: class CompositeNode : public Node
3: {
4:     public:
5:         ...
6:         Node* GetChild(int idx);
7:         void AddChild(Node* node);
8:         int GetChildCount() const;
9:     protected:
10:         Nodes m_Children;
11: };
这个类很简单,就提供了一个子节点的成员变量,并包含一些访问接口,下面我们来看看如何来做一个选择节点,我们需要做两个部分,一个是选择节点(Select Node),一个是选择任务(Select Task),选择节点用来生成选择任务,这个是共享节点型行为树的基本概念,后面就不再赘述了。
1: class SelectorTask : public Task
2: {
3: public:
4:     SelectorTask(Node* pNode)
5:         : Task(pNode)
6:         , m_LastBehavior(-1)
7:     {}
8:     CompositeNode& GetCompositeNode(){
9:         return *dynamic_cast<CompositeNode*>(m_pNode);
10:     }
11:     virtual void OnInit(const BevNodeInputParam& inputParam)
12:     {}
13:     virtual BevRunningStatus OnUpdate(const BevNodeInputParam& inputParam, BevNodeOutputParam& outputParam)
14:     {
15:         CompositeNode& comNode = GetCompositeNode();
16:         if(comNode.GetChildCount() == 0)
17:             return k_BRS_Failure;
18:
19:         if(!m_CurrentBehavior.HasInstalled())
20:         {
21:             m_LastBehavior = 0;
22:             m_CurrentBehavior.Install(*(comNode.GetChild(m_LastBehavior)));
23:         }
24:         BevRunningStatus status = m_CurrentBehavior.Update(inputParam, outputParam);
25:         if(status != k_BRS_Failure)
26:         {
27:             return status;
28:         }
29:         for(int i = 0; i < comNode.GetChildCount(); ++i)
30:         {
31:             if(m_LastBehavior == i)
32:                 continue;
33:
34:             m_CurrentBehavior.Install(*(comNode.GetChild(i)));
35:             BevRunningStatus status = m_CurrentBehavior.Update(inputParam, outputParam);
36:             if(status != k_BRS_Failure)
37:             {
38:                 m_LastBehavior = i;
39:                 return status;
40:             }
41:         }
42:         return k_BRS_Failure;;
43:     }
44:     virtual void OnTerminate(const BevNodeInputParam& inputParam)
45:     {
46:         m_LastBehavior = -1;
47:         m_CurrentBehavior.Uninstall();
48:     };
49:
50: private:
51:     int             m_LastBehavior;
52:     Behavior m_CurrentBehavior;
53: };
54: class CompositeNode_Selector : public CompositeNode
55: {
56: public:
57:     virtual Task* CreateTask()
58:     {
59:         return new SelectorTask(this);
60:     }
61:     virtual void DestroyTask(Task* pTask)
62:     {
63:         SelectorTask* pTest = dynamic_cast<SelectorTask*>(pTask);
64:         D_CHECK(pTest);
65:         D_SafeDelete(pTest);
66:     }
67: };
从上面的代码中,可以看到,这是一个带优先级的选择节点,因为我们每次都是从第一个子节点开始寻找可运行的节点,当找到后,就中断寻找的过程,和上一个版本不同的是,在现在这个版本的实现中,我们并没有引入“前提”的概念,而是通过在Update的返回值来确定当前节点是否可以运行,这样的话,我们需要在行为节点的Update中实现对于“前提”的检查,并返回正确的返回值。序列和并行的实现方法大同小异,只要了解了这些节点的基本概念,就可以很容易的写出相关的代码。大家可以在文章的最后找到下载链接,我也强烈建议大家自己实现一下,以加深理解。另外,我们在实现每一个Task类的时候,都需要一个相应的Node类来生成这个Task,而这些Node类的结构基本是一样的,所以我为此专门定义了一个宏来生成这个Node类(不过使用起来,还不是很方便,有改进的余地)
1: #define DEF_TERMINATE_NODE(name, task) \
2:     class Node##_##name : public Node {\
3:     public:\
4:         virtual Task* CreateTask(){\
5:             return new task(this);\
6:         }\
7:         virtual void DestroyTask(Task* pTask){ \
8:             task* pTest = dynamic_cast<task*>(pTask);\
9:             D_CHECK(pTest);\
10:             D_SafeDelete(pTest);    \
11:         };\
12:     };
13:
14: #define CREATE_TERMINATE_NODE(name) new Node##_##name()
最后我用新的行为树重写了那些行为节点(参考这里),实现了同样的功能。从内存的使用量来看,共享节点型行为树,大大减少了内存的消耗,比较适合存在大量的智能体的情况。和原来的行为树实现有一点不同的是,并且特别要注意的是,共享节点型的行为树,在运行过程中,会不断的创建和销毁执行完毕的Task(对于不可运行的Task,它也会先创建,然后Update一下,再销毁),所以就会存在大量的小内存的分配和释放,如果没有很好的内存管理机制,这样的分配和释放,会造成大量的内存碎片,在我的程序中,我并没有做内存的部分,但如果要使用这样的行为树模式在实际的开发中的话,必须要在内存的管理上做一些额外的工作,比如使用内存池,小内存表等等。下载地址:GoogleCode下载点(exe文件夹中已包含可执行文件)也可用svn通过以下地址来得:http://tsiu.googlecode.com/svn/branches/blogver/编译方法:用VS2005以上打开,选择Debug NoDx或者Release NoDx,将BTTInit2.cpp以及TAI_BevTree.cpp加入编译(方法:在此文件上右键-属性-在编译中排出,选否),将BTTInit3.cpp移除编译(方法:在此文件上右键-属性-在编译中排出,选是),这样编译后就会得到由原本的行为树版本所作的示例程序,反之,编译后就是用新的行为树版本做的示例程序相关代码:TAI_BevTree2.h关于TsiUTsiU是我一直在维护的一个自己用的小型的框架,我平时做的一些AI的sample,或者一些工具,都会基于这个框架,TsiU有一些基本的UI控件库,网络模块库,GDI绘图模块,D3D绘图模块等等,可以快速的做成一个小型的示例程序,很方便(具体可参考SampleApps里的例子程序),并且整个架构是用Object的方式来组织,非常容易理解和扩展。整个框架很轻量化,基本就是做了一些底层的基本的功能,这样我在平时做东西的时候,就不需要重新写底层了,把精力都放在高层的实现了。以后分享代码都会基于这个框架,大家也可以通过svn来随时update到我最新的改动。下图就是TsiU里的几个工程介绍,代码不多,大家想看的也可以自己看一下:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  行为树