C++ 版本的 行为树的简单实现
如果你想转载这篇文章呢,请严格按照以下格式注明出处和作者
出处:http://www.cnblogs.com/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,第二个参数是对应的节点名,可以不是类名,不过我推荐你还是用类名,只有查找的时候好找
可能放上一段代码更直观一些
//回到主人身边 class AINodeGotoOwnerSide : public AINodeBase { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | // // 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() { } 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; public : virtual void addChildNode(AINodeBase * node); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 | // // 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) m_nodeHlper = new AINodeHelper(); return m_nodeHlper; } void AINodeHelper::registerNodeCreate( int type, BaseNodeCreate create) { m_type2Create[type] = create; } void AINodeHelper::registerNodeName( int type, std::string name) { m_type2Name[type] = name; } AINodeBase * AINodeHelper::CreateNode( int type) { AINodeBase * nodeBase = NULL; do { map< int , BaseNodeCreate>::iterator iter = m_type2Create.find(type); if (iter == m_type2Create.end()) break ; nodeBase = (*iter).second(); if (nodeBase == NULL) break ; map< int , string>::iterator iter_name = m_type2Name.find(type); if (iter_name != m_type2Name.end()) { string & name = (*iter_name).second; nodeBase->setName(name); } } while (0); return nodeBase; } AINodeBase * AINodeHelper::CreateNodeTree(vector<AINodeDescribe> des, void * host) { if (des.size() == 0) return NULL; #if COMMAND_LINE && AI_DEBUG cout << "CreateNodeTree all count = " << des.size() << endl; #endif #if COCOS2D && AI_DEBUG CCLOG( "CreateNodeTree all count = %d" , ( int )des.size()); #endif map< int , AINodeBase *> m_type2Create; AINodeBase * rootNode = NULL; for (vector<AINodeDescribe>::iterator iter = des.begin(); iter != des.end(); ++iter) { AINodeDescribe &item = (*iter); AINodeBase * node = CreateNode(item.AINodeTypeId); #if COMMAND_LINE && AI_DEBUG cout << "CreateNodeTree " << item.AINodeTypeId << endl; #endif #if COCOS2D && AI_DEBUG CCLOG( "CreateNodeTree %d" , item.AINodeTypeId); #endif if (node == NULL) continue ; node->m_host = host; //注入宿主 if ( strlen (item.Describe) != 0) { node->setDescribe(item.Describe); } m_type2Create[item.Id] = node; if (item.ParentId == 0) { rootNode = node; } else { do { AINodeBase * parentNode = m_type2Create[item.ParentId]; if (parentNode == NULL) break ; AIListNode * listParentNode = dynamic_cast <AIListNode *>(parentNode); if (listParentNode != NULL) { listParentNode->addChildNode(node); break ; } AISingleNode * singleNode = dynamic_cast <AISingleNode *>(parentNode); if (singleNode != NULL) { singleNode->setChildNode(node); break ; } } while (0); } } return rootNode; } void AINodeHelper::printAITree(AINodeBase * node, int level) { ostringstream oss; for ( int i = 0; i < level; ++i) { oss << "\t" ; } oss << node->getName() << " " << node->getDescribe() << " " << node; #if COMMAND_LINE cout << oss.str().c_str() << endl;; #endif #if COCOS2D CCLOG(oss.str().c_str()); #endif do { AIListNode * listNode = dynamic_cast <AIListNode *>(node); if (listNode != NULL) { vector<AINodeBase *> & childs = listNode->getChildNodes(); if (childs.size() > 0) { for (std::vector<AINodeBase *>::iterator i = childs.begin(); i != childs.end(); ++i) { printAITree(*i, level + 1); } } break ; } AISingleNode * singleNode = dynamic_cast <AISingleNode *>(node); if (singleNode != NULL) { AINodeBase * child = singleNode->getChildNode(); if (child != NULL) { printAITree(child, level + 1); } } } while (0); } int MemoryManagementObject::getReferenceCount() { return m_mmo_referenceCount; } void MemoryManagementObject::retain() { ++m_mmo_referenceCount; } void MemoryManagementObject::release() { --m_mmo_referenceCount; if (m_mmo_referenceCount <= 0) { delete this ; } } //最根层节点 void AINodeBase::destroy() { #if COMMAND_LINE && AI_DEBUG cout << "destroy " << getName() << " " << this << endl; #endif #if COCOS2D && AI_DEBUG CCLOG( "destroy %s %p" , getName().c_str(), this ); #endif release(); } void AINodeBase::setDescribe(std::string describe) { m_nodeDescribe = describe; } string AINodeBase::getDescribe() { return m_nodeDescribe; } void AINodeBase::setName(string name) { m_nodeName = name; } string AINodeBase::getName() { return m_nodeName; } //列表节点 void AIListNode::addChildNode(AINodeBase * node) { m_childNodes.push_back(node); } std::vector<AINodeBase *> & AIListNode::getChildNodes() { return m_childNodes; } void AIListNode::destroy() { if (m_childNodes.size() > 0) { for (vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter) { (*iter)->destroy(); } } AINodeBase::destroy(); } //单条节点 void AISingleNode::setChildNode(AINodeBase * node) { if (m_childNode != node) { if (m_childNode != NULL) m_childNode->destroy(); m_childNode = node; } } AINodeBase * AISingleNode::getChildNode() { return m_childNode; } void AISingleNode::destroy() { if (m_childNode != NULL) { m_childNode->destroy(); } AINodeBase::destroy(); } //选择节点 AINodeRegister<AISelectNode> AISelectNode::reg(ANBT_SELECT, "AISelectNode" ); bool AISelectNode::invoke( int level, bool isLog) { bool success = false ; do { if (m_childNodes.size() == 0) break ; for (vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter) { AINodeBase * node = (*iter); bool inv = node->invoke(level + 1, isLog); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG ostringstream oss; for ( int i = 0; i < level; ++i) { oss << " " ; } oss << node->getName() << " invoke " << inv; #endif #if COMMAND_LINE && AI_DEBUG if (isLog) { cout << oss.str().c_str() << endl; } #endif #if COCOS2D && AI_DEBUG if (isLog) { CCLOG( "%s" , oss.str().c_str()); } #endif if (inv) { success = true ; break ; } } } while ( false ); return success; } //顺序节点 AINodeRegister<AISequenceNode> AISequenceNode::reg(ANBT_SEQUENCE, "AISequenceNode" ); bool AISequenceNode::invoke( int level, bool isLog) { bool success = true ; do { for (vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter) { AINodeBase * node = (*iter); bool inv = node->invoke(level + 1, isLog); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG ostringstream oss; for ( int i = 0; i < level; ++i) { oss << " " ; } oss << node->getName() << " invoke " << inv; #endif #if COMMAND_LINE && AI_DEBUG if (isLog) { cout << oss.str() << endl; } #endif #if COCOS2D && AI_DEBUG if (isLog) { CCLOG( "%s" , oss.str().c_str()); } #endif if (inv == false ) { success = false ; break ; } } } while ( false ); return success; } //取反节点 AINodeRegister<AINotNode> AINotNode::reg(ANBT_NOT, "AINotNode" ); bool AINotNode::invoke( int level, bool isLog) { bool success = false ; do { if (m_childNode == NULL) break ; success = !(m_childNode->invoke(level + 1, isLog)); } while ( false ); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG ostringstream oss; for ( int i = 0; i < level; ++i) { oss << " " ; } if (m_childNode != NULL) { oss << m_childNode->getName() << " invoke " << !success; } else { oss << "no child" ; } #endif #if COMMAND_LINE && AI_DEBUG if (isLog) { cout << oss.str() << endl; } #endif #if COCOS2D && AI_DEBUG if (isLog) { CCLOG( "no child" ); } #endif return success; } |
可能这个描述还不是很明确,你可以给我留言,我尽量给你解答
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?