📄 有限状态机.txt
字号:
状态转换所需的输入有:
#define INPUT_ID_PLAYER_SEEN 1
#define INPUT_ID_PLAYER_ATTACK 2
#define INPUT_ID_PLAYER_GONE 3
#define INPUT_ID_MONSTER_HURT 4
#define INPUT_ID_MONSTER_HEALED 5
以上是五个状态的标识符。
我们就要声明5个FSMstate的实例,每一个实例代表一个状态和与之有关的操作。假设我们先处理状态STATE_ID_MAD
类成员变量m_iStateID就等于STATE_ID_MAD类成员变量m_usNumberOfTransition就是可由这个状态转换成的状态的个数,前面有一个表,其中有两个状态可以由这个状态产生,它们分别是STATE_ID_UNCARING和STATE_ID_RAGE。
这时,m_usNumberOfTransition等于2。
m_piInputs是一个指针变量,它保存与这个状态相关的输入,在前面的表中我们知道与STATE_ID_MAD相关的输入为
INPUT_ID_MONSTER_HURT和INPUT_ID_MONSTER_HEALED,因此m_piInputs中存放的是这两个数据。
而m_piOutputState存放的是与STATE_ID_MAD相关的状态,即STATE_ID_RAGE和STATE_ID_UNCARING,这样,m_piOutputState中存放的数据便是这两个值。
以上是对成员变量的解释,下面解释成员函数:
构造函数
FSMstate::FSMstate(int iStateID,unsigned usTransitions)
{
if(!usTransitions) //如果给出的转换数量为0,就算为1
m_usNumberOfTransitions=1;
else
m_usNumberOfTransitions=usTransitions;
//将状态的ID保存起来
m_iStateID=iStateID;
//分配内存空间
try
{
m_piInputs=new int[m_usNumberOfTransitions];
for(int i=0;i<m_usNumberOfTransitions;++i)
m_piInputs[i]=0;
}
catch(...)
{
throw;
}
try
{
m_piOutputState=new int[m_usNumberOfTransition];
for(int i=0;i<m_usNumberOfTransitions;++i)
m_piOutputState[i]=0;
}
catch(...)
{
delete [] m_piInputs;
throw;
}
}
这就是构造函数,在FSMstate类中共有四个成员变量,在这个函数中全部被初始化了。FSMstate是一个类,是否还记得MONSTER的状态(如MAD、UNCARING)。这个类就是实现对MONSTER的一个状态的管理的。假如这个状态是STATE_ID_MAD, 与这个状态相关的状态有两个,上面已经讲过了。这时我们给成员变量赋值,在这个具体例子中它们的值如下:
m_usNumberOfTransition=2
m_piInput[0]=0;
m_piInput[1]=0;
m_piOutputState[0]=0;
m_piOutputState[1]=0;
m_iStateID=STATE_ID_MAD;
析构函数:
FSMState::~FSMState()
{
delete [] m_piInputs;
delete [] m_piOutputState;
}
析构函数将动态分配的存储空间释放了。
void FSMstate::AddTransition(int iInput,int iOutputID)
{
for(int i=0;i<m_usNumberOfTransitions;++i)
if(!m_piOutputState[i]) break;
if(i<m_usNumberOfTransition)
{
m_piOutputState[i]=iOutputID;
m_piInputs[i]=iInput;
}
}
这个函数给两个前面构造函数动态分配的空间加入数据,首先要找到两个数组中找到适当的位置,之后,如果位置是合法的
我们就可以把数据加入这两个数组中。因为STATE_ID_MAD与两个状态有关,因此,我们可以调用两次这个函数,把这两个状态加入到类中:
AddTransition(INPUT_ID_MONSTER_HURT,STATE_ID_RAGE);
AddTransition(INPUT_ID_MONSTER_HEALED,STATE_ID_UNCARING)
这样,与状态STATE_ID_MAD相关的“状态”和“输入”也加入了。
void FSMstate::DeleteTransition(int iOutputID)
{
// 遍历每一个输出状态
for(int i=0;i<m_usNumberOfTransitions;++i)
{
//如果找到输出状态,退出循环
if(m_piOutputState[i]==iOutputID)
break;
}
//如果没有找到输出状态,返回
if(i>=m_usNumberOfTransitions)
return;
//将输出状态的内容置0
m_piInputs[i]=0;
m_piOutputState[i]=0;
//被删除的输出状态的后面的输出状态前移
for(;i<(m_usNumberOfTransition-1);++i)
{
if(!m_piOUtputState[i])
break;
m_piInputs[i]=m_piInputs[i+1];
m_piOutputState[i]=m_piOutputState[i+1];
}
//最后面的输出状态置0
m_piInputs[i]=0;
m_piOutputState[i]=0;
}
这个函数是要删除与一个状态相关的输出状态。设一个状态STATE_ID_MAD,与之相关的状态有两个STATE_ID_RAGE,STATE_ID_UNCARING,当然这是经过初始化以及前面的添加状态函数之后,产生了这两个相关的状态。你想删除哪一个?如果你想删除相关的输出状态,只要在删除函数中指出那个状态即可,例如:
DeleteTransition(STATE_ID_RAGE);
你就可以删除输出状态STATE_ID_RAGE了。
int FSMstate::GetOutput(int iInput)
{
//先给输出状态赋值(如果未找到与输入对应的输出状态时,返回这个值)
int iOutputID=m_iStateID;
//遍历输出状态
for(int i=0;i<m_usNumberOfTransitions;++i)
{
//如果没找到,退出循环
if(!m_piOutputState[i])
break;
//如果找到了与“输入”相对应的“输出状态”,进行赋值。
if(iInput==m_piInputs[i])
{
iOutputID=m_piOutputState[i];
break;
}
}
//返回“输出状态”
return(iOutputID);
}
这个函数功能是返回与“输入”相对应的“输出状态”的标识。如果没有与“输入”相对应的“输出状态”,返回原来的状态,如果有与之对应的“输出状态”,返回这个状态的ID。
下面定义的是FSMclass,这个类用于维护FSMstate对象集合。
class FSMclass
{
State_Map m_map; //包括了状态机的所有状态
int m_iCurrentState; //当前状态的ID
public:
FSMclass(int iStateID); //初始化状态
~FSMclass()
//返回当前状态ID
int GetCurrentState() {return m_iCurrentState;}
//设置当前状态ID
void SetCurrentState(int iStateID) {m_iCurrentState=iStateID;}
//返回FSMstate对象指针
FSMstate* GetState(int iStateID);
//增加状态对象指针
void AddState(FSMstate* pState);
//删除状态对象指针
void DeleteState(int iStateID);
//根据“当前状态”和“输入”完成“状态”的转换。
int StateTransition(int iInput);
};
FSMclass::m_map是FSMstate对象的集合,是从STL<map>中实现的。
FSMclass::m_iCurrentState是FSMstate对象的状态标识,是“有限状态机”的当前状态。
FSMclass::GetCurrentState()可以用之访问当前的FSMstate对象的状态的标识符。
FSMclass::SetCurrentState()可以设置当前FSMstate对象的状态的标识符。
FSMclass::GetState()可以取得有限状态机中的任何FSMstate对象的指针。
FSMclass::AddState()增加有限状态机中的FSMstate对象。
FSMclass::DeleteState()删除有限状态机中的FSMstate对象
FSMclass::StateTransition()初始化状态转换,根据输入返回输出状态。
这个类使用了STL,我不知道它怎么用:)。听说是高人才使用它,高人起码要写过上万行的代码。因此不能详细介绍这个类了
总之,可以这么理解这两个类FSMstate,FSMclass.FSMstate代表了一个状态以及和状态相关的数据和操作。如在MONSTER中有五个状态,我们就要声明五个类的对象,每个对象中包括了与这个状态相关的状态,输入和各种转换函数。可以说FSMstate是对每一个状态的封装(包括相关数据和操作),游戏中的对象有多少状态,就要声明多少个FSMstate对象。而FSMclass则是对这若干个FSMstate对象(这个例子中MONSTER有五个状态)进行的封装。在FSMclass中指明了若干个FSMstate中哪一个是当前的MONSTER拥有的状态并且可以设置,得到以及删除状态,并且可以进行状态间的转换。
总之:游戏中的MONSTER有多少状态,游戏中就要声明多少的FSMstate对象,每一个FSMstate对象包括了与特定的状态相关的数据和操作。而FSMclass只有一个,它用于协调若干个FSMstate之间的关系和操作。
下面是如何在游戏中使用两个类的例子:
首先是创建FSMstate对象(若干个),有多少状态就要循环多少次,下面是增加STATE_ID_UNCARING状态的例子:
FSMstate* pFSMstate=NULL;
//创建状态
try
{
//第一个参数是增加状态的标识,第二个参数指明了与这个
//状态相关的状态的个数。
pFSMstate=new FSMstate(STATE_ID_UNCARING,2);
}
catch(...)
{
throw;
}
//之后给这个状态加入相关的“输入”和“输出状态”
pFSMstate->AddTransition(INPUT_ID_PLAYER_SEEN,STATE_ID_ANNOYED);
pFSMstate->AddTransition(INPUT_ID_PLAYER_ATTACKS,STATE_ID_MAD);
这个函数指明了与特定状态相关的“输入”和“输出状态”
比如第一个函数,它表明如果我要输入一个INPUT_ID_PLAYER_SEEN,这时就会产生一个输出状态,STATE_ID_ANNOYED。
我们应该为每一个状态做上面的事情,这里就略过了。之后我们要声明一个FSMclass对象,用于协调上面的FSMstate对象之间的关系。
try
{
m_pFSMclass=new FSMclass(STATE_ID_UNCARING);
}
catch(...)
{
throw;
}
上面指明了MONSTER的当前状态是STATE_ID_UNCARING最后将FSMstate对象分别加入到FSMclass中。
下面介绍如何使用FSMclass
使用十分简单,只要我们给出一个“输入”,之后,我们便可以得到一个“输出状态”,根据这个“输出状态”我们执行相应的操作,最后,把这个“输出状态”变成MONSTER的当前状态。
在游戏中发生了一些事情,如玩游戏的人指出他控制的人进攻MONSTER(用鼠标点击了MONSTER),这时会产生一个“输入”iInputID=INPUT_ID_PLAYER_ATTACK;
这时,我们调用状态转换函数:
m_iOutputState=m_pFSMclass->StateTransition(iInputID);
这时,我们的“输入”对MONSTER产生了刺激,产生了一个“输出状态”。这时我们根据这个输出状态调用相应的代码执行就可以了,这时的MONSTER好像有应反了,我们说它有了简单的智能。
if(m_iOutputState==STATE_ID_MAD)
{
//some code for the monster to act mad
}
当然,我们也应该把其它状态执行的操作也写出来,但只写一个就可以了。使用这个状态机就是这么简单。总之,FSMclass不是全部的人工智能,相反,它只是一个框架,一个开始智能需要的还很多。只要你可以分出“状态”,并且知道什么“输入”产生什么“输出状态”就可以了,当然这是一个游戏的规则,策划应当完成这个部分?
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -