无处不在的开关语句实现游戏状态?

执行摘要

在Game State FSM上有没有很好的教程,特别是基于OOP / OOD的,而不是基于switch-statement的?

概要

除了使用switch语句之外,是否有创建游戏状态引擎的实际过程,最好是带有例子? 维基百科的事件驱动FSM文章只显示了一个switch语句,关于FSMs的文章是严格基于理论的,没有实际的例子。

背景

目前我所有的游戏都使用一个非常简单的游戏状态“引擎”,它使用开关语句。 这是非常混乱,可怕地违反DRY,并没有很好的规模。

每个状态都被视为一个等级,并且必须在整个游戏循环的所有三个点上进行检查,即input,处理和渲染。 如果添加了级别或屏幕,则必须将其添加到所有的switch语句中。

用于教育目的的休闲娱乐的实际例子(严重的是,这是几年前的一个学校项目):

enum STATE { LOADING, TITLE, LEVEL_ONE, LEVEL_TWO, // ... WIN, GAME_OVER, CREDITS, }GAME_STATE; void Input() { // ... switch(GAME_STATE) { case LOADING: break; case TITLE: break; case LEVEL_ONE: case LEVEL_TWO: // ... InputCommon(); break; case WIN: case GAME_OVER: if(keyboard->KeyDown(KEY_ENTER)) GAME_STATE = CREDITS; break; case CREDITS: if(keyboard->isKeyPressed()) quit = true; break; } } void Processing(int dead) { // ... switch(GAME_STATE) { case LOADING: break; case TITLE: break; case LEVEL_ONE: case LEVEL_TWO: // ... ProcessLevel(); if(GAME_STATE == LEVEL_SEVEN) { // ... } break; case WIN: break; case GAME_OVER: break; case CREDITS: break; } } void Render() { // ... switch(GAME_STATE) { case LOADING: break; case TITLE: sh->Draw(_gw->GetBackBuffer(), sh->Index(0)); break; case LEVEL_ONE: case LEVEL_TWO: // ... // ... break; case WIN: sh->Draw(_gw->GetBackBuffer(), sh->Index(3)); break; case GAME_OVER: sh->Draw(_gw->GetBackBuffer(), sh->Index(1)); break; case CREDITS: sh->Draw(_gw->GetBackBuffer(), sh->Index(2)); break; } //End render process, display to screen. _gw->EndRender(); } 

如果你不介意参考一本书,那么在“ 游戏编程宝典”5中有一篇名为“大规模基于堆栈的状态机”的文章,它提供了一个很好的实现你所寻找的东西的方法。 过去几年我一直在使用它的一个变种,并且喜欢它很多。 我从这篇文章中得到的大部分见解,我已经在这个答案中讨论过了 ,所以没有必要再重复一遍。

但是,既然拿起那本书可能不容易,这里有一个免费的select。 Gamebryo教科书的第一章描述了一个基于Game Programming Gems 5的实现,您也可以在这里find源代码 。

(更新:上面的链接不再有效,我似乎无法find他们已经搬迁的地方,如果有人这样做,请编辑这个答案)

最后,尽管在C#和XNA中, 这个来自Microsoft的示例提供了一个简单的实现,应该很容易跟踪并转换为C ++。

第一步实际上是把你的switch语句的内容移动到数据文件中。 等级布局等应该在地图文件中。 特殊级别的逻辑可以移动到脚本中,或者在最前面移入他们自己的函数/类中。 您可以在启动时使用级别管理器注册这些数据文件或C函数,然后您的switch语句变为m_CurrentLevel-> update()。

请注意,很多逻辑确实应该是常见的。 每个级别都没有理由有自己的input处理,因为好的无刺激游戏通常只有一种玩法。 当然,例外情况比比皆是,特别是如果你实现菜单有一个游戏状态。 尽管如此,抽象的东西已经消失了:有一个菜单处理程序可以运行在任何活动的菜单状态,游戏状态的游戏处理程序等等。

如果有疑问,只要考虑(良好)面向对象devise。

我倾向于创建一个所有各种游戏状态inheritance的Scene对象,并在Main对象中创建一个名为“currentScene”的variables。 然后,而不是在主循环中的switch语句,只需调用currentScene-> Update(); 每个场景都会知道input处理和渲染需要做什么。 要切换游戏状态,只需切换currentScene中的哪个场景。

看看Boost状态图库。 我最近开始使用它,经过多年的尝试,实现FSMs稍微不同的方式,这绝对是我目前最喜欢的! 有一些古怪的东西阻止了它的完美(例如,参见'state'类和'simple_state'),但是它提供了一些非常有用的特性,并且允许你编写比等效的更加紧凑和expression的状态机基于开关的代码。

http://www.boost.org/doc/libs/1_49_0/libs/statechart/doc/tutorial.html

是的,也有一种叫AI的行为树,用于AI。 如果你想研究他们,我会建议从这里开始:

行为树简介

BjörnKnafla以非常简单的方式解释了这个系统涉及的内容。 我目前正在实施这个实习。 行为树的优势在于,您正在做决定而不是设置状态

例如,假设你想为Pacman中的幽灵建立一些AI。 你想要他们跟随吃豆子,如果他们看到他,当他们看不见他时,不要再跟着他。 所以,在行为树中:

 Walking -- Am I following Pacman? -- -- Have I lost sight of Pacman? -- -- -- Stop following Pacman. -- -- -- <End> -- (Else) -- -- Do I see Pacman? -- -- -- Follow Pacman -- -- -- <End> 

所以,我们第一次走树:

 Walking Am I following Pacman? -> No Else Do I see Pacman? -> Yes Follow Pacman End 

下一帧:

 Walking Am I following Pacman? -> Yes Have I lost sight of Pacman? -> No End 

诀窍是每个框架都会对行为树进行评估,但只会影响行为 。 所以,在Ghost AI的情况下,它只能确定走到哪里,但实际上并没有走到任何地方。

相反,你有一个单独的代码,看起来像这样:

 if (m_BrainData->target == StateTarget::ePacman) { MoveTowardsPacman(); } else { MoveRandomly(); } 

为什么这么重要? 因为现在你可以混合状态。 现在,如果你想让幽灵在看到吃豆子的时候运行一个“害怕”的animation,那么你只需要评估幽灵的“大脑”:

 if ( m_BrainData->target == StateTarget::ePacman && m_BrainData->emotion == StateEmotion::ePanic ) { m_AnimationPanic->Render(); if (m_AnimationPanic->Finished()) { m_BrainData->emotion = StateEmotion::eFleeing; } } else { m_Animation->Render(); } 

然后将这些状态添加到行为树中:

 Walking -- Am I following Pacman? -- -- Have I lost sight of Pacman? -- -- -- Stop following Pacman. -- -- -- <End> -- (Else) -- -- Do I see Pacman? -- -- -- Is Pacman powered up? -- -- -- -- Am I not fleeing? -- -- -- -- -- Panic -- -- -- -- -- <End> -- -- -- -- (Else) -- -- -- -- -- Flee -- -- -- -- -- <End> -- -- -- (Else) -- -- -- -- Follow Pacman -- -- -- <End>