C++版本的⾏为树的简单实现
如果你想转载这篇⽂章呢,请严格按照以下格式注明出处和作者
出处:wwwblogs/anxin1225/p/4827294.html
作者:Anxin1225、Bianchx、Linker(其实他们都是⼀个⼈。。)
⾏为树是⼀种简洁明了的整理业务逻辑的有效⽅法。⾄于他的好处,不做赘述。
由于项⽬的需要,所以实现了⼀个⾮常简单的⾏为树,来应对我们的需求。之所以说简单,是因为我并没有实现很多控制节点,⽽只是实现了最基础的业务的三个节点⽽已。⾄于其他的你觉得有⽤的控制节点,可以⾃⼰修改出来。
简单说说我实现的节点:基础节点、单条节点、列表节点、选择节点、顺序节点、取反节点。这⼏个节点分为相对较为基础的节点,和业务节点。基础的节点包括:基础节点、单条节点、列表节点。基础的节点的主要作⽤是定义,定义最基础的调⽤⽅法和关于⼦节点应该怎么样保存。业务节点包含选择节点、顺序节点和取反节点。他们的继承关系如下:基础节点是最基础的节点;单条节点和列表节点继承⾃基础节点;选择节点和顺序节点继承⾃列表节点;取反节点继承⾃单条节点。
来简单说⼀下各个节点的作⽤
基础节点:
1、invoke函数,被调⽤时,返回true或者false
2、destroy函数,节点被释放时会递归式的释放所有依附于此节点的⼦节点和曾⼦节点
3、设置和获取 Describe 的函数,⽤于打印AITree时的结构描述
4、设置和获取 Name 的函数,⽤于打印AITree时的名称描述和调⽤时,递归描述的打印
列表节点:
1、包含⼀个有序的⼦集列表,可以添加和获取⼦集列表的引⽤
单条节点:
1、包含⼀个⼦集节点,可以设置和获取⼦集节点
选择节点:
1、被调⽤时,如果没有⼦集节点则会直接返回false
2、调⽤时,会依次从前往后执⾏,任何⼀个⼦集节点返回了true,则终⽌循环,直接返回true
3、当所有的⼦集节点都没有返回true时,则会返回false
顺序节点:
1、被调⽤时,如果没有⼦集节点则会直接返回false
2、调⽤时,会依次从前往后执⾏,任何⼀个⼦集节点返回了false,则终⽌循环,直接返回false
3、当所有的⼦集节点都没有返回false时,则会返回true
取反节点:
1、被调⽤时,如果没有⼦集节点则直接返回false
2、存在⼦集节点时,则会调⽤⼦集节点,并且将结果取反并返回
实现了这些节点之后就可以实现以下图⽰的⼤部分功能(⼿⽐较残,⼜加上⾝边没有⼯具,所以⽤⽂字的表⽰吧)
先简单解释⼀下这个图什么意思,第⼀个是节点的名字,注⼊的时候写的,可以是中⽂也可以是英⽂,这个⽆所谓,毕竟只有这个地⽅在⽤。第⼆个参数是当前实例的描述,如果是⽤来帮助你理解这个树的
再简单解释⼀下,这段逻辑是什么意思。这个是⼀个宠物的逻辑,如果附近有⾦币呢,他就取捡⾦币;如果没有⾦币呢并且很长时间没有捡到⾦币并且很长时间没有回到主⼈⾝边了,那就回到主⼈⾝边,否则就随便⾛⾛。
其实这个逻辑真的挺简单的,如果,是按照普通的⽅式来写的话。就会在各种状态之间判断条件然后各种跳转执⾏。这样能实现,不过后期的维护可能更加费劲⼀些,如果使⽤配置⾏为树则相对简单⼀些,在修改的时候只需要添加新的分⽀或者减掉原来的分⽀就可以了。逻辑也相对更加清晰。
然后简单说明⼀下,怎么在我的这个⼩玩意⾥边扩展⾃⼰的东西。
1、在AITreeNodeType添加⼀个新的枚举,主要是⽤来确定Id⽤的,注⼊的时候⽤的(⾄于什么是注⼊⼀会再说)
2、然后继承⽐较基本的节点,⼀般情况下继承最基础的三个就好,最常⽤的就是AINodeBase,那我们就那AINodeBase来举例
3、然后实现virtual bool invoke(int level = 0, bool isLog = false);⽅法,level代表从根节点开始这是第⼏
层调⽤,⼀般⽤作Log的时候前边有⼏个空格,isLog代表是否打印Log,你完全可以忽视这两个参数不管,当然你要实现对应的功能最好遵守这两个参数的约定,当然不遵守我也没有意见。
4、在类中添加⼀个私有的static AINodeRegister<;类名> reg;然后在Cpp⽂件中编写AINodeRegister<;类名
> AINodeReleaseSkill::reg(NodeId, NodeName);来实现注⼊,第⼀个参数是之前你获得的Id,第⼆个参数是对应的节点名,可以不是类名,不过我推荐你还是⽤类名,只有查的时候好
可能放上⼀段代码更直观⼀些
{
private:
static AINodeRegister<AINodeGotoOwnerSide> reg;
public:
virtual bool invoke(int level = 0, bool isLog = false);
};
AINodeRegister<AINodeGotoOwnerSide> AINodeGotoOwnerSide::reg(ANT_GOTO_OWNER_SIDE, "AINodeGotoOwnerSide");
bool AINodeGotoOwnerSide::invoke(int level, bool isLog)
{
return rand() % 100 > 20;
}
说完了累的扩展,应该简单说⼀下什么是注⼊了,简单点说,就是我写了⼀个公开的帮助函数,⽤来接受Id跟⼀个创建节点的函数指针,然后把它们保存在的字典中,你需要调⽤的时候,我就从字典⾥边当初注⼊的函数指针,然后调⽤它,给你⼀个实例。⾄于为什么要写⼀个静态的AINodeRegister泛型类,是因为静态的初始化实在程序启动的时候会初始化,应⽤这个特性,我们就可以在初始化的时候把,想要初始化的内容注⼊到内存中。
其实说到这个地⽅,主要的逻辑已经基本上说的差不多了。还有⼀些其他的⽅⾯,⽐如说树的组装如何
处理,如果是挨个编写他们之间的引⽤应该也会很⿇烦。并且,使⽤这种结构处理业务逻辑的时候,业务内容就会分的乱七⼋糟什么地⽅都有,调试也可能会成为问题。
实现Id跟类型之间的关联之后就可以通过描述类型来创建类了,最后的实现如下
AINodeDescribe des[] = {
AINodeDescribe(1, 0, ANBT_SELECT, "根节点"),
AINodeDescribe(2, 1, ANBT_SEQUENCE, "是否拾取⾦币的判定节点"),
AINodeDescribe(5, 2, ANT_RELEASE_SKILL, "附近是否存在⾦币"),
AINodeDescribe(6, 2, ANT_PICKING_UP_COINS, "捡取⾦币节点"),
AINodeDescribe(3, 1, ANBT_SEQUENCE, "是否回到主⼈⾝边的判定节点"),
AINodeDescribe(7, 3, ANT_RELEASE_SKILL, "是不是很长时间没有见到⾦币了"),
AINodeDescribe(8, 3, ANT_PICKING_UP_COINS, "是不是很长时间没有回到主⼈⾝边了"),
AINodeDescribe(9, 3, ANT_PICKING_UP_COINS, "回到主⼈⾝边的执⾏节点"),
AINodeDescribe(4, 1, ANT_PICKING_UP_COINS, "没事随便逛逛吧"),
};
int desCount = sizeof(des) / sizeof(AINodeDescribe);
vector<AINodeDescribe> des_vtr;
for (int i = 0; i < desCount; ++i)
{
des_vtr.push_back(des[i]);
}
AINodeBase * rootNode = AINodeHelper::sharedHelper()->CreateNodeTree(des_vtr);
AINodeDescribe初始化的时候接受四个参数:当前Id,⽗节点Id,当前节点创建的树节点具体类型,当前节点实例的描述。其中⽗节点如果是0的时候则会被当做根节点返回,这个⼀点要有⼀个哦,不然会直接返回NULL,并且申请的所有节点都会造成内存泄露。
起始这个地⽅可以吧参数都写到⽂件中,然后通过⽂件来进⾏初始化,不过,我这个地⽅只是为了演⽰⽤,所以直接写死也没有关系,不过你在⽤的时候,我推荐你写⼀个读取⽂件配置的⽅法,效果会更好。(因为你可以吧这段的逻辑整理直接做⼀个编辑器,让策划来进⾏对应的内容的整理。)
对了,这个地⽅,你可能是按照⾃⼰的想法来描写的这个⽂件,但是实际的执⾏结果可能跟你的想法并不⼀样,你可以进⾏如下处理来进⾏验证
cout << "\n状态结构组织图 \n" << endl;
AINodeHelper::sharedHelper()->printAITree(rootNode);
cout << "\n状态结构组织图 \n" << endl;
输出的结果呢,就是最上边那张图了
剩下的还存在⼀个问题,那就是调试问题了,我不可能在这么多内容中下断点,那跟下毒没啥区别。所以我们需要有⼀种⽅式来打印各个节点的运⾏结果。这个我的处理如下
for (int i = 0; i < 10; ++i)
{
cout << "调⽤树开始" << endl;
rootNode->invoke(0, true);
cout << "调⽤树结束" << endl;
}
其中invoke的第⼀个参数的意思为最基础的节点的届位,第⼆个参数为是否打印Log,如果不想调试的话,两个参数都不要填就可以。贴⼀下相关的⽂件
AITree.h
//
//  AITree.h
//  KPGranny2
//
//  Created by bianchx on 15/9/15.
//
//
#ifndef __KPGranny2__AITree__
#define __KPGranny2__AITree__
#include <stdio.h>
#include <vector>
#include <map>
#include <string>
#include "AITreeNodeType.h"
#pragma mark =============== public Helper Action ==================
class AINodeBase;
typedef AINodeBase * (* BaseNodeCreate)();
struct AINodeDescribe
{
AINodeDescribe()
{
memset(this, 0, sizeof(AINodeDescribe));
}
AINodeDescribe(int id, int pId, int typeId, char * describe = NULL)
:Id(id)
,ParentId(pId)
,AINodeTypeId(typeId)
{
memset(Describe, 0, sizeof(Describe));
if(describe != NULL && strlen(describe) < sizeof(Describe) / sizeof(char))
{
strcpy(Describe, describe);
}
}
int Id;            //当期节点Id
int ParentId;      //⽗节点Id
int AINodeTypeId;  //智能节点类型Id
char Describe[256];  //节点名称
};
class AINodeHelper
{
private:
static AINodeHelper * m_nodeHlper;
std::map<int, BaseNodeCreate> m_type2Create;
std::map<int, std::string> m_type2Name;
public:
static AINodeHelper * sharedHelper();
void registerNodeCreate(int type, BaseNodeCreate create);
void registerNodeName(int type, std::string name);
AINodeBase * CreateNode(int type);          //创建节点
AINodeBase * CreateNodeTree(std::vector<AINodeDescribe> des, void * host = NULL);
void printAITree(AINodeBase * node, int level = 0);
};
template <class T>
class AINodeRegister
public:
static AINodeBase * CreateT()
{
return new T();
}
AINodeRegister(int type, std::string name = "")
{
AINodeHelper * helper = AINodeHelper::sharedHelper();
helper->registerNodeCreate(type, &AINodeRegister::CreateT);
if(name != "")
helper->registerNodeName(type, name);
}
};
#pragma mark ================== 具体的内容 ================= enum AINodeBaseType
{
ANBT_SELECT,        //选择节点
ANBT_SEQUENCE,      //顺序节点
ANBT_NOT,          //取反节点
};
class MemoryManagementObject
{
private:
int m_mmo_referenceCount;
public:
MemoryManagementObject()
:m_mmo_referenceCount(1)
{
}
virtual ~MemoryManagementObject()
{
}
int getReferenceCount();
void retain();
void release();
};
class AINodeBase : public MemoryManagementObject
{
protected:
std::string m_nodeName;
std::string m_nodeDescribe;
public:
AINodeBase()
:m_host(NULL)
,m_nodeName("AINodeBase")
,m_nodeDescribe("")
{
}
virtual ~AINodeBase() { }
include意思void * m_host;      //AI的宿主
virtual bool invoke(int level = 0, bool isLog = false) { return false; }
virtual void destroy();
virtual void setDescribe(std::string describe);
virtual std::string getDescribe();
virtual void setName(std::string name);
virtual std::string getName();
};
//列表节点
class AIListNode : public AINodeBase
{
protected:
std::vector<AINodeBase *> m_childNodes;
virtual std::vector<AINodeBase *> & getChildNodes();    virtual void destroy();
};
//单条节点
class AISingleNode : public AINodeBase
{
protected:
AINodeBase * m_childNode;
public:
AISingleNode()
:m_childNode(NULL)
{ }
virtual void setChildNode(AINodeBase * node);
virtual AINodeBase * getChildNode();
virtual void destroy();
};
//选择节点
class AISelectNode : public AIListNode
{
private:
static AINodeRegister<AISelectNode> reg; public:
virtual bool invoke(int level = 0, bool isLog = false); };
/
/顺序节点
class AISequenceNode : public AIListNode
{
private:
static AINodeRegister<AISequenceNode> reg; public:
virtual bool invoke(int level = 0, bool isLog = false); };
//取反节点
class AINotNode : public AISingleNode
{
private:
static AINodeRegister<AINotNode> reg;
public:
virtual bool invoke(int level = 0, bool isLog = false); };
#endif /* defined(__KPGranny2__AITree__) */
  AITree.cpp
//
//  AITree.cpp
//  KPGranny2
//
//  Created by bianchx on 15/9/15.
//
//
#include "AITree.h"
#include <iostream>
#include <sstream>
#define COMMAND_LINE 0
#define COCOS2D 1
#define AI_DEBUG 1
#if COCOS2D
#include "cocos2d.h"
#endif
using namespace std;
AINodeHelper * AINodeHelper::m_nodeHlper(NULL); AINodeHelper * AINodeHelper::sharedHelper()
{
if(m_nodeHlper == NULL)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。