如何让你的蠢小人动起来——C++中包含优先级复杂条件FSM的实现
来源:互联网 发布:淘宝旧杂志回收 编辑:程序博客网 时间:2024/05/20 14:28
有限状态机(FSM)最大的优点在于实现函数的复用,通过条件(condition)与状态(state)的连接,让逻辑主体在不同的状态对象中切换,执行其中的功能代码。在引入了优先级(priority)的概念后,FSM可以被应用在一些复合条件的AI应用场景中,简单地来说,在某个状态下,同时满足多个条件时按照优先级排序,决定最终执行的条件判断和随后的状态切换。本文将通过一个简单的应用实例,初步探讨在C++中如何实现复杂条件的FSM.
首先,我们想要实现的FSM结构如下:
可以清除的看到这个简单AI的完整逻辑:完成初始化后进入第一个state: searchTarget, 并执行其中的功能函数,搜索目标,根据返回结果判定后续的两个条件hastarget和notarget是否成立,因为这两个条件是互斥的,所以priority是否相同没有影响;当notarget成立的时候,进入idle状态,AI进行无目的的游荡,经过一段时间后,当timeUP_2成立时,重新进行searchTarget;当hastarget成立时进入chaseTarget状态,chaseTarget由另一个时间条件timeUp驱动,执行追逐目标的功能,当目标死亡(targetDead)或者超出范围(tooLong)的时候进入resetTarget状态,并最终回到searchTarget状态;在idle或者chaseTarget这两个状态下,当满足agentDead条件时,进入最后的Finish状态,结束状态机并进行清理。
上述所有的条件都可以具备自身的priority,比如在chaseTarget状态下,对应的三个条件agentDead, targetDead和tooLong,其中agentDead的优先级显然应该最高,我们将其设置为6,targetDead和tooLong可以具有相同的优先级,如果某个时刻三个条件同时满足,则优先切换到priority最高的agentDead对应的Finish状态。下面我们看看如何在C++里实现这一简单FSM的模拟。
首先我们利用工厂模式建立一个状态/条件和枚举变量之间的映射:
typedefvoid*(*factoryFun) (void);
classmyFactory
{
private:
staticmyFactory* m_index;
map<string,factoryFun> myFactoryMap;
public:
staticmyFactory* getSingleInstance()
{
if(m_index ==NULL)
{
m_index=newmyFactory();
}
returnm_index;
}
void*getClassByname(string classname);
voidregistionFun(string classname, factoryFunmyfactoryFun);
};
classmyRegistion
{
public:
myRegistion(stringclassname,factoryFunmyfactoryFun)
{
myFactory::getSingleInstance()->registionFun(classname,myfactoryFun);
}
};
#defineREGISTER(classname) \
classname* FSM##classname() \
{ \
returnnewclassname(); \
} \
myRegistionmyFSM##classname(#classname, (factoryFun)FSM##classname())
//..........................................................................................//
myFactory*myFactory::m_index=NULL;
void*myFactory::getClassByname(stringclassname)
{
map<string,factoryFun>::const_iteratorit;
it=myFactoryMap.find(classname);
if(it== myFactoryMap.end())
{
returnNULL;
}
else
{
returnit->second;
}
}
voidmyFactory::registionFun(stringclassname,factoryFunmyFun)
{
myFactoryMap.insert(make_pair(classname,myFun));
}
有了这个映射表 myFactoryMap,我们就可以方便地用代表状态的枚举值获得对应的状态和条件对象了。
接下来,我们要建立state的基类:
//state的基类
classinit_FSM
{
public:
//init_FSM();
public:
string nowState;
inta;
clock_t tick1;
clock_t tick2;
clock_t tick3;
public:
virtualboolprossesing(init_FSM* myEntry);
};
boolinit_FSM::prossesing(init_FSM*myEntry)
{
myEntry->tick1= clock();
myEntry->tick2= clock();
myEntry->a= 0;
cout<<myEntry->a<<endl;
cout<<"agent的所有变量初始化"<< endl;
returntrue;
}
REGISTER(init_FSM);
为了方便,我们就使用第一个状态init_FSM作为基类,它定义了FSM中使用的所有局部变量,并且将其初始化。
然后利用宏我们建立所有的派生类:
#defineCREATECHILDSTATE(classname) \
class classname : publicinit_FSM \
{ \
public: \
boolprossesing(init_FSM* myEntry); \
};
注册这些派生类,例如:
CREATECHILDSTATE(searchTarget_FSM);
REGISTER(searchTarget_FSM);
Condition的建立和注册方法与state相同,实际上两者只是功能上的划分,没有本质的区别。
CREATECHILDSTATE(timeUp_FSMC);
REGISTER(timeUp_FSMC);
接下来我们要实现state与condition中的功能函数processing,比如:
boolchaseTarget_FSM::prossesing(init_FSM*myEntry)
{
myEntry->tick1= clock();
myEntry->a= 3;
cout<<myEntry->a<<endl;
cout<<"实现追逐目标的过程"<< endl;
returntrue;
}
好了,到这里为止我们的condition和state所有的类都已经建立好并且注册到工厂里了,那么接下来我们要怎样实现一个state和它对应的下游condition之间的映射表呢?我们可以简单地用XML将文章开头的FSM描述出来,然后将其加载到vs中变成一个map.先来看看这张XML:
<?xml version="1.0" encoding="UTF-8"?><Project name="MonsterFSM"><!-- Starting--><state ID = "init_FSM"><to ID = "searchTarget_FSM"><condition ID = "true_FSMC" Priority = "0"></condition></to></state><!-- searching for target--><state ID = "searchTarget_FSM"><to ID = "idle_FSM"><condition ID = "noTarget_FSMC" Priority = "0"></condition></to><to ID = "chaseTarget_FSM"><condition ID = "hasTarget_FSMC" Priority = "1"></condition></to></state><!-- wardering around--><state ID = "idle_FSM"><to ID = "searchTarget_FSM"><condition ID = "timeUp_2_FSMC" Priority = "0"></condition></to><to ID = "finish_FSM"><condition ID = "agentDead_FSMC" Priority = "1"></condition></to></state><!-- moving to target position while it still alive--><state ID = "chaseTarget_FSM"><to ID = "chaseTarget_FSM"><condition ID = "timeUp_FSMC" Priority = "0"></condition></to><to ID = "finish_FSM"><condition ID = "agentDead_FSMC" Priority = "6"></condition></to><to ID = "resetTarget_FSM"><condition ID = "tooLong_FSMC" Priority = "1"></condition></to><to ID = "resetTarget_FSM"><condition ID = "targetDead_FSMC" Priority = "1"></condition></to></state><!-- reset target--><state ID = "resetTarget_FSM"><to ID = "searchTarget_FSM"><condition ID = "true_FSMC" Priority = "0"></condition></to></state> <!-- finish--><state ID = "finish_FSM"></state></Project>
可以看到,如果我们将每一个state都当作一个node来处理的话,那么每个state下都会对应数个子node,而且我们可以在这些node的下级中配置它们对应的condition和priority,这样我们这张XML的表格和前述的FSM关系图就是等效的。
现在我们将XML加载到vs里,这里我用的是比较常用的tinyxml,代码如下:
enum successEnum
{
FAILURE,
SUCCESS
};
structcAndsFormat
{
string condition;
string state;
intpriority;
};
//利用priority对vector进行冒泡排序
void popVector(vector<cAndsFormat>&myVector)
{
vector<cAndsFormat>::iterator it;
vector<cAndsFormat>::iterator itBack = myVector.begin();
cAndsFormat itBUFF;
for(it=myVector.begin();it!=myVector.end();it++)
{
for(itBack=myVector.begin();itBack!=myVector.end();itBack++)
{
if(it->priority > itBack->priority)
{
itBUFF=*it;
*it=*itBack;
*itBack=itBUFF;
}
}
}
}
vector<string>stateNameArray;
map<string,vector<cAndsFormat>>myFlexMap;
//读取XML中的状态和条件信息并建立map
successEnumloadXML()
{
TiXmlDocument doc;
if(!doc.LoadFile("monsterFSM.xml"))
{
cerr<<doc.ErrorDesc()<< endl;
returnFAILURE;
}
//project
TiXmlElement* root = doc.FirstChildElement();
if(root ==NULL)
{
cerr<<"Failed to load file: No root element."<<endl;
doc.Clear();
returnFAILURE;
}
for(TiXmlElement* elem = root->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
{
//元素名称状态名称
string eleName = elem->Attribute("ID");
//将找到的state名称存放
stateNameArray.push_back(eleName);
//遍历每个state下所有的子状态 to
TiXmlElement* stateChild;
TiXmlElement* conditionChild;
string conditionName, stateName;
cAndsFormat myObj;
vector<cAndsFormat>myFlex;
for(TiXmlElement* stateChild = elem->FirstChildElement();stateChild != NULL; stateChild =stateChild->NextSiblingElement())
{
stateName=stateChild->Attribute("ID");
conditionChild =stateChild->FirstChildElement();
conditionName=conditionChild->Attribute("ID");
myObj.condition=conditionName;
myObj.state=stateName;
myObj.priority= atoi(conditionChild->Attribute("Priority"));
myFlex.push_back(myObj);
}
//按照priority将找到的所有子状态冒泡排序
if(myFlex.size() > 0)
{
popVector(myFlex);
}
//加入map中
myFlexMap.insert(make_pair(eleName,myFlex));
}
doc.Clear();
returnSUCCESS;
}
这样,这张state与condition之间的映射表myFlexMap就建立好了。在使用这个FSM的时候,首先建立init_FSM的对象并将其保存在容器里,比如vector<init_FSM*>myEntryArray,在循环里遍历这个类就可以实现FSM的运作了。
int main()
{
loadXML();
init_FSM* myEntry = newinit_FSM();
myEntryArray.push_back(myEntry);
//执行init的prossesing语句
myEntry->prossesing(myEntry);
//将状态置为init
myEntry->nowState="init_FSM";
vector<init_FSM*>::iterator it;
while(true)
{
for(it= myEntryArray.begin();it!=myEntryArray.end();)
{
if((*it)->nowState=="finish_FSM")
{
it=myEntryArray.erase(it);
}
else
{
string stateName = (*it)->nowState;
init_FSM* currentState = (init_FSM*)myFactory::getSingleInstance()->getClassByname(stateName);
vector<cAndsFormat>vectorBuffer = myFlexMap.find(stateName)->second;
vector<cAndsFormat>::iterator itCheck;
for(itCheck= vectorBuffer.begin(); itCheck!=vectorBuffer.end(); itCheck++)
{
init_FSM* myCondition = (init_FSM*)myFactory::getSingleInstance()->getClassByname(itCheck->condition);
if(myCondition->prossesing(*it))
{
init_FSM* myState = (init_FSM*)myFactory::getSingleInstance()->getClassByname(itCheck->state);
myState->prossesing(*it);
(*it)->nowState=itCheck->state;
break;
}
}
it++;
}
}
if(myEntryArray.size() < 1)
{
break;
}
}
}
- 如何让你的蠢小人动起来——C++中包含优先级复杂条件FSM的实现
- 沟通——让你的团队动起来
- 让你的网页文字动起来。。。
- 让你的图标动起来
- ViewDragHelper让你的app动起来
- 让你的web程序“动”起来。
- 让你的程序“动”起来。
- 如何让SVG的path动起来
- 让你的文字动起来——Marquee用法详解
- 让你的文字动起来——Marquee用法详解
- 让你的tableView动起来 —— iOS8 SpringAnimation应用
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- JavaWeb学习总结——Web应用中使用JavaMail发送邮件
- 知识收录(记录平时使用查询到的资料)
- Python PIL & base64
- [C++] 虚指针,虚表,虚函数地址打印
- 开源项目地址
- 如何让你的蠢小人动起来——C++中包含优先级复杂条件FSM的实现
- textview 属性大全
- C++Primer &、*符号的多重定义问题
- 此博文记录前端开发中遇到的问题
- 工作3年的程序员应该具备什么技能
- qsort效率探究
- 关于Android原生集成5+webview,监听webview返回时,执行两次onkey方法问题的解决
- 在 Linux 下用 mkdir 命令来创建目录和子目录
- Graphics.MeasureString用指定的字体绘制时测量指定的字符串