Finite State Machine(FSM)是一种人工智能的实现方式,对象在不同的状态间转换,且某一时刻只能处于一个状态;FSM中对象的行为是由我们自己设置的流程, 在加入消息功能后, 不同对象之间可以通过消息通信的方式来完成互动, 因此会显然非常只能;
参考<<游戏人工只能编程案例精粹>>,这里写了一个类似的渔夫与妻子的一天的简单生活案例;
状态 state
单例模板如下, 程序中状态都设计为单例,这样转换过程不用重新生成释放对象;
template<typename T> class Singleton { protected: Singleton(){}; ~Singleton(){}; static T* pInstance; public: static T* Instance() { if(!pInstance) pInstance = new T(); return pInstance; } }; template<typename T> T* Singleton
::pInstance = 0; 参考<<设计模式>>状态模式, 设计了了state基类, 考虑到状态对于渔夫和妻子会不同,所以这里的state采用模板类;
template<typename T> class State { public: State(void){}; virtual ~State(void){}; virtual void vExecute(T* afisher)=0; virtual void vEnter(T* afisher)=0; virtual void vExit(T* afisher)=0; };
基于上面的基类, 我们可以将渔夫与妻子的不同具体状态继承于base;如:
// 渔夫的状态 class FishingState : public State
,public Singleton { public: virtual void vExit(Fisher* afisher); virtual void vExecute(Fisher* afisher); virtual void vEnter(Fisher* afisher); }; class EatFoodState :public State ,public Singleton { public: //.... }; class GoHomeState : public State ,public Singleton { public: //.... }; // 渔夫妻子的状态 class WfWashState : public State ,public Singleton { public: virtual void vExit(FisherWife* afisher); virtual void vExecute(FisherWife* afisher); virtual void vEnter(FisherWife* afisher); }; class WfCookState : public State ,public Singleton { public: //.... }; class WfBashRoom : public State ,public Singleton { public: //.... }; 状态管理类
一个对象身上只要有一个状态管理类, 里面放者当前状态, 全局状态, 上一个状态;之所以要全局状态, 是为了处理一些全程可能转换的状态,如渔夫唱歌, 在家可以, 打渔也可以, 如果在每个状态中写上唱歌的状态转换就重复也比较麻烦, 添加全局状态可以在tick函数中直接使用, 就免去了上面的麻烦,; 而上一个状态则可以用来处理返回, 即如唱完歌应当回到当前状态而不是直接进入下一个状态;
statemachine采用模板来处理, 对于渔夫与妻子传入相应的类型进去:
// StateMachine.h template<typename T> class StateMachine { public: inline State
* GetCurState() { return mCurState; } // .... another get set function void ChangeState(State * pst); void Tick(); private: T* mOwner; State * mCurState; State * mPreState; State * mGlobalState; }; // template function need defined in the .h file template<typename T> void StateMachine ::ChangeState(State * pst) {e->vExit(mOwner); mCurState = pst; mCurState->vEnter(mOwner); } template<typename T> void StateMachine ::Tick() { if(mGlobalState) mGlobalState->vExecute(mOwner); if(mCurState) mCurState->vExecute(mOwner); } 对象基类
struct Telegram; //通信包结构 class GameObject { private: int mId; //每个对象的唯一识别ID public: GameObject(int Id):mId(Id){}; virtual ~GameObject(void); inline int GetId(){return mId;} virtual void Tick() = 0; //每帧调用的函数 virtual void HandleMessage(const Telegram& pMsg) = 0; //消息处理函数 };
渔夫与妻子都继承并实现该类:
// fisher class Fisher:public GameObject { private: State
* mCurState; //当前状态 int mNumFish; // 鱼数量 int mMoney; int mFatigue; // 体力 public: void FindFish(); // 打渔 void SaleFish(); // 卖鱼 inline bool FishBinFull(){return mNumFish >= MAX_NUM_FISH;} void ChangeState(State * st); /* 其它函数 */ virtual void Tick(); virtual void HandleMessage(const Telegram& pMsg); Fisher(int id); }; // fisherwife class FisherWife : public GameObject { public: FisherWife(int id); virtual void Tick(); inline StateMachine * GetFSM(){return mFSM;} virtual void HandleMessage(const Telegram& pMsg); private: StateMachine * mFSM; }; 渔夫和妻子的实现代码如下:
// fisher void Fisher::Tick() { #ifdef _WIN32 SetTextColor(FOREGROUND_RED|FOREGROUND_INTENSITY); #endif if(mCurState) mCurState->vExecute(this); #ifndef _WIN32 cout<
#endif } /* other function */ // state change void Fisher::ChangeState(State * st) { mCurState->vExit(this); mCurState = st; mCurState->vEnter(this); } // msg is dealed in fisher class but state class void Fisher::HandleMessage(const Telegram& pMsg) { switch(pMsg.Id) { case MSG_BACKHOME: break; case MSG_BREADOK: break; case MSG_FOODREADY: cout<<"Thank you my dear for making the meal!"< break; default: cout<<"Can't deal MSG"< // fisher wife void FisherWife::Tick() { #ifdef _WIN32 SetTextColor(FOREGROUND_GREEN|FOREGROUND_INTENSITY); #endif GetFSM()->Tick(); #ifndef _WIN32 cout< #endif } void FisherWife::HandleMessage(const Telegram& pMsg) { switch(pMsg.Id) { case MSG_BACKHOME: cout<<"o my dear, you are back home!, you must be hungry, let me make meal for you !"< ChangeState(GWfCookState); break; case MSG_BREADOK: cout<<"oh, my dear, the bread is OK!"< DispatchMessage(0, FISHER_WIFE,FISHER,MSG_FOODREADY,NULL, 0); break; case MSG_FOODREADY: break; default: cout<<"Can't deal MSG"< 全局对象管理
该类是单例类, 管理了所有的对象,通过ID来识别; 有了这个管理类, 在派发消息时就可以通过ID来获得对象指针, 从而通知对象;
class ObjectManager : public Singleton
{ public: ObjectManager(); virtual ~ObjectManager(); GameObject* FindObjpt(int id); void Remove(GameObject* ply); void RegisterObj(GameObject* ply); // 生成新对象时调用这个注册到管理器 private: std::map<int,GameObject*> AllGmObjMap; }; 消息类
消息结构
struct Telegram { MSG_ID Id; //the defined msg id in a enum structure int Receiver; int Sender; int MsgLen; int DispatchTime; // mark the time that the msg is send (for delay send msg) void* Data; Telegram(MSG_ID id):Id(id), Receiver(-1), Sender(-1), MsgLen(0), DispatchTime(0), Data(NULL) { } // overload the ==, > symbol because the msg will put in a priority queue inline bool operator==(const Telegram& t1) { return DispatchTime == t1.DispatchTime; } }; // 由于比较, 以时间为优先队列的排列顺序 inline bool operator<(const Telegram& t1,const Telegram& t2) { return t1.DispatchTime < t2.DispatchTime; }
消息派发类
将需要立即处理的消息在当前帧就调用; 将延迟发送的消息存储到一个消息队列中,在每个主循环中遍历一遍,如果时间过期了则立刻处理;
class GameObject; class MessageDispatcher : public Singleton
{ public: MessageDispatcher(); virtual ~MessageDispatcher(); // immediate msg be send void DispatchMessage(const int delay, const int Sender, const int Receiver, const MSG_ID msg, void* pInfo, const int len); // delay send msg , detected in each main tick void DispatchDelayMessage(); void Discharge(GameObject* pRecv, const Telegram& msg); protected: private: std::set mMsgQueue; //use as a priority queue }; 下面是几个个主要的消息处理派发函数:
// 通过对象指针, 让对象调用消息处理函数来处理当前的消息 void MessageDispatcher::Discharge(GameObject* pRecv, const Telegram& msg) { assert(pRecv); pRecv->HandleMessage(msg); } // immediate msg be send void MessageDispatcher::DispatchMessage(const int delay, const int Sender, const int Receiver, const MSG_ID msgId, void* pInfo, const int len) { GameObject* pReceiver = GObjMgr->FindObjpt(Receiver); if(!pReceiver) { cout<<"Not Find Message Receiver!"<
return; } Telegram msg(msgId); msg.MsgLen = len; msg.DispatchTime = -1; msg.Receiver = Receiver; msg.Sender = Sender; msg.Data = pInfo; // not delay if(delay <= 0) { cout<<" receiver: "< " get a imme msg from "<< Sender< else { msg.DispatchTime = NowTime + delay; mMsgQueue.insert(msg); } } // delay send msg , detected in each tick void MessageDispatcher::DispatchDelayMessage() { std::set ::iterator itr = mMsgQueue.begin(); // loop will be break once when the dispatchtime is bigger than nowtime // because the set is a priority queue sorted by time while(itr!=mMsgQueue.end() && itr->DispatchTime >= NowTime && itr->DispatchTime > 0) { Discharge(GObjMgr->FindObjpt(itr->Receiver),*itr); mMsgQueue.erase(itr); itr = mMsgQueue.begin(); } }
// define the Global Singleton class object
#define GFishingState FishingState::Instance()
#define GEatFoodState EatFoodState::Instance()
// other fisher state...
#define GWfWashState WfWashState::Instance()
// other fisher wife state...
#define GObjMgr ObjectManager::Instance()
#define GMsgDisptchr MessageDispatcher::Instance()
const int MAX_NUM_FISH = 150;
// Gameobject id
enum ObjPly
{
FISHER,
FISHER_WIFE,
};
extern unsigned long NowTime; // gloabl current time, update at each loop
// cross platform at linux or windows with different header
#ifdef _WIN32
#include
#include
#include
//default text colors can be found in wincon.h
inline void SetTextColor(WORD colors)
{
HANDLE hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, colors);
}
#else
#include
#include
#define Sleep(s) sleep(s/1000)
#endif
// msg defined
enum MSG_ID
{
MSG_BACKHOME,
MSG_BREADOK,
MSG_FOODREADY,
};
// update global time
unsigned long NowTime = 0;
void UpdateTIme()
{
#ifdef _WIN32
NowTime = GetTickCount();
#else
struct timeval ts;
struct timezone tus;
gettimeofday(&ts,&tus);
NowTime = ts.tv_sec*1000 + ts.tv_usec/1000;
#endif
}
// main
int main()
{
UpdateTIme();
Fisher fisr(FISHER);
GObjMgr->RegisterObj(&fisr);
FisherWife wife(FISHER_WIFE);
GObjMgr->RegisterObj(&wife);
fisr.ChangeState(GFishingState);
while(1)
{
fisr.Tick();
wife.Tick();
GMsgDisptchr->DispatchDelayMessage(); // msg traversal
Sleep(1000);
}
}