⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 有限状态机.txt

📁 有限状态机FSM思想广泛应用于硬件控制电路设计
💻 TXT
📖 第 1 页 / 共 2 页
字号:

状态转换所需的输入有:
#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 + -