http://www.cnblogs.com/kongtiao/archive/2011/09/25/2190226.html
介绍2种有限状态机
http://www.bubuko.com/infodetail-470540.html
介绍有限状态机是如何实现AI控制等
---------------------------------------------------------------------------------------------------------
下面这两篇文章出自同一个博主,博文图文并茂,很生动。
http://blog.csdn.net/turkeyzhou/article/details/7695813 游戏中的状态机
http://blog.csdn.net/turkeyzhou/article/details/7695782 游戏引擎剖析
这个文章介绍了很多游戏设计的内容,很值得一看。其中提到了主循环,和游戏主逻辑设计。
主循环如何实现依赖于你使用的系统。对于一个基本的控制台程序,它可能是一个简单的while循环中调用各个函数:
- while( !finished ) {
- handle_events();
- update();
- render();
- sleep(20);
- }
注意到这里的sleep函数。它使得代码休眠一小段时间不致于占用全部的CPU。
有些系统完全不想让用户代码那些写,它们使用了回调系统以强制程序员常规的释放CPU。这样,当应用程序执行后,程序员注册一些函数给系统在每次循环中回调:
- void main(void) {
- OS_register_event_handler( myEventHandler );
- OS_register_update_function( myUpdate );
- OS_register_render_function( myRender );
- }
一旦程序执行后,根据必要情况,那些函数会间隔性的被调用。IPHONE是最接近后面这个例子。你可以在下一章和IPHONE SDK中看到它。
游戏状态管理器
一个好的视频游戏不仅有一组动作来维持游戏:它会提供一个主菜单允许玩家来设定选项和开始一个新游戏或者继续上次的游戏;制作群屏将会显示所有辛勤制作这款游戏的人员的名字;而且如果你的游戏没有用户指南,应该一个帮助区域会给用户一些提示告诉他们应该做什么。
以上任何一种场合都是一种游戏状态,并且代表中一段独立的应用程序代码片段。例如,用户在主菜单调用的函数与导航与用户在制作群屏调用的是完全不同的,所以程序逻辑也是不同的。特别的是,在主菜单,你可能会放一张图片和一些菜单,并且等待用户选择哪个选项,而在制作群屏,你将会把游戏制作人员的名字描绘在屏幕上,并且等待用户输入,将游戏状态从制作群屏改为主菜单。最后,在游戏中状态,将会渲染实际的游戏并等待用户的输入以与游戏逻辑进行交互。
以上的所有游戏状态都负责相应用户输入、将内容渲染到屏幕、并为该游戏状态提供相对应的应用程序逻辑的任务。你可能注意到了这些任务都来自于之前讨论的主循环中,这是因为它们就是同样的任务。但是,每个状态都会以它们自己的方式来实现这些任务,这也就是为什么要保持他们独立。你不必在主菜单代码中寻找处理游戏中的事件的代码。
状态机
状态管理器是一个状态机,这意味着它跟踪着现在的游戏状态。当应用程序执行后,状态机会创建基本的状态信息。它接着创建各种状态需要的信息,并在离开每种状态时销毁暂时存储的信息。
状态机维护着大量不同对象的状态。一个明显的状态是用户所在屏幕的状态(主菜单、游戏中等)。但是如果你有一个有着人工智能的对象在屏幕上时,状态机也可以用来管理它的“睡眠”、“攻击”、“死亡”状态。
什么是正确的游戏状态管理器结构?让我们看看一些状态机并决定哪种最适合我们。
有许多实现状态机的方式,最基本的是一个简单的switch语句:
- class StateManager {
- void main_loop() {
- switch(myState) {
- case STATE_01:
- state01_handle_event();
- state01_update();
- state01_render;
- break;
- case STATE_02:
- state02_handle_event();
- state02_update();
- state02_render;
- break;
- case STATE_03:
- state03_handle_event();
- state03_update();
- state03_render;
- break;
- }
- }
- };
改变状态时所有需要做的事情就是改变myState变量的值并返回到循环的开始处。但是,正如你看到的,当我们加入越来越多的状态时,代码块会变得越来越大。而且更糟的是,为了使程序按我们预期的执行,我们需要在程序进入或离开某个状态时执行整个任务块,初始化该状态特定的变量,载入新的资源(比如图片)和释放前一个状态载入的资源。在这个简单的switch语句中,我们需要加入更多的程序块并保证不会漏掉任何一个。
以上是一些简单重复的劳动,但是我们的状态管理器需要更好的解决方案。下面一种更好的实现方式是使用函数指针:
- class StateManager {
- //the function pointer:
- void (*m_stateHandleEventFPTR) (void);
- void (*m_stateUpdateFPTR)(void);
- void (*m_stateRenderFPTR)(void);
- void main_loop() {
- stateHandleEventFPTR();
- m_stateUpdateFPTR();
- m_stateRenderFPTR();
- }
- void change_state( void (*newHandleEventFPTR)(void),
- void (*newUpdateFPTR)(void),
- void (*newRenderFPTR)(void)
- ) {
- m_stateHandleEventFPTR = newHandleEventFPTR;
- m_stateUpdateFPTR = newUpdateFPTR;
- m_stateRenderFPTR = newRenderFPTR
- }
- };
现在,即使我们处理再多状态,主循环也足够小而且简单。但是,这种解决方案依然不能帮助我们很好的解决初始化与释放状态。因为每种游戏状态不仅包含代码,还有各自的资源,所以更恰当的做法是将游戏状态作为对象的属性来考虑。因此,接下来,我们将会看看面向对象(OOP)的实现。
我们首先创建一个表示游戏状态的类:
- class GameState
- {
- GameState(); //constructor
- virtual ~GameState(); //destructor
- virtual void Handle_Event();
- virtual void Update();
- virtual void Render();
- };
接着,我们改变我们的状态管理器以使用这个类:
- class StateManager {
- GameState* m_state;
- void main_loop() {
- m_state->Handle_Event();
- m_state->Update();
- m_state->Render();
- }
- void change_state( GameState* newState ) {
- delete m_state;
- m_state = newState;
- }
- };
最后,我们创建一个指定具体游戏状态的类:
- class State_MainMenu : public GameState
- {
- int m_currMenuOption;
- State_MainMenu();
- ~State_MainMenu();
- void Handle_Event();
- void Update();
- void Render();
- };
当游戏状态以类来表示时,每个游戏状态都可以存储它特有的变量在该类中。该类也可以它的构造函数中载入任何资源并在析构函数中释放这些资源。
而且,这个系统保持着我们的代码有很好的组织结构,因为我们需要将游戏状态代码分别放在各个文件中。如果你在查找主菜单代码,你只需要打开State_MainMenu类。而且OOP解决方案使得代码更容易重用。
这个看起来是最适合我们需要的,所以我们决定使用它来作为我们的状态管理器。