游戏开发中的状态机模式(翻译整理)(上篇)

标签:
it游戏开发设计模式 |
状态机设计模式本身有比较多的基础理论在里面,但在这里,我们游戏所用的有限状态机FSMs,我们用一种简单的方式,来学习和分析。当我们知道了,为什么要用,有什么优点后,对状态机的理解就容易多了。当然在下面的例子中,我们的代码并不是完美的,有很多细节是需要自己去完善的。原贴:
http://gameprogrammingpatterns.com/state.html
现在就让我们开始吧:)
假象一下,我们在开发一个横版卷轴类游戏。我们的工作时实现一个英雄角色在游戏世界里面运动。也就是说,我们的角色需要响应我们的玩家的输入。如按下B键,角色会跳起来,代码如下:
void Heroine::handleInput(Input input){
发现BUG没有?
上面的代码没有禁止掉空中再次跳跃的情况,如果玩家一直按B,那么角色就会一直往天上飞,不落下来了。最简单的修正这个BUG的方式就是,添加一个IsJumping_的boolean变量。当玩家跳跃的时候做检查:
void Heroine::handleInput(Input input){
下一步,当角色在地面上时,玩家按下 Down 键时,让角色蹲下,当玩家释放开Down键时,角色站立起来:
void Heroine::handleInput(Input input){
发现BUG没有?
通过上面实现的代码,玩家可以做以下的操作:
按下Down键,角色蹲下。
1.按下B 键,角色蹲着的地方飞了起来。
3角色在空中,释放开Down键后。
这几步操作完后,就会发现,玩家在跳跃的空中,恢复到了站立的动画。
为了解决这个BUG,我们只有再加一个标志。。。
void Heroine::handleInput(Input input){
下一步,如果能让玩家在跳跃到空中的时候,按down键,来一个俯冲攻击,应该很酷:
void Heroine::handleInput(Input input){
找BUG时间又到了,找到了没?
我们之前保证了玩家在跳跃的时候不能再次跳跃,但是现在玩家在俯冲的时候,又可以跳跃了。我们又只有再加一个条件。。。是不是觉得要疯掉了。
这个时候,我们应该能感觉的很明显了,我们现在的解决方案是有问题。每一次在这个段代码上添加新的需求,就会破坏之前的一些逻辑。按照这种情况持续下去,就算是吧需求做完了,后面应该会有一堆的BUG等着我们去修改。
这个时候 有限状态机 就是我们的救命稻草了。
在经历一些纠结和挫折之后,你把桌面上的所有东西都清理干净,然后只留下一个笔和一张纸,开始画一个流程图。你画上了一个个盒子,这些盒子代表着玩家的各种行为状态:站立中,跳跃中,下蹲中,俯冲中。当按键按下,从一个状态变化到另一个状态时,我们用一个条件加一根线,指向我们将切换到的新的状态。
http://s6/mw690/002hBfPnzy7dC1xsSFvf5&690
恭喜你,你已经成功的创建了有限状态机了。其实这个来自于计算机分支科学:图灵机或者自动机理论。在这些理论中,有限状态机是最简单的理论之一。
它的要点如下:
l
l
l
l
例如,在图中,角色在Standing状态时,收到按下 down 键事件,那么就会转换到Ducking状态。当玩家在Jumping状态时,按下down键事件,那么就会转换到Diving状态。
如果在当前状态收到一个没有定义的输入事件,那么忽略该事件。
简单的实现方式,用Enums和Swithces
其中一个问题是我们的Heroine类中的boolean变量们,像isJumping_和isDucking_他们应该是不能同时为true的。当我们有一堆变量都是boolean,但是同一时刻只能一个为True的话,就说明我们需要用Enums了。
enum State
{
};
用枚举State一个就代替了我们的一堆的是否标识的布尔变量。在前面的代码中,我们是用按照输入来作为条件判断,然后再切的状态。这样能把一个输入下,需要做的事情放在了一起,但是却让多个状态的代码混淆在了一起。现在我们想要一个状态的代码整合在一起,那么我就需要先用状态来做switch:
void Heroine::handleInput(Input input){
这个改变看起来好像没有太大的用,但事实上却是有很大进步。虽然代码中,还是存在不少的条件分支,但是我们将不容易理解的状态这个相应的代码整合在了一起。大家可以对比下前面的代码和这段代码,新的代码在理解上清晰明了了很多。这个就是最简单的实现FSM的方法。
我们的游戏可能会再进一步的扩展,如,我们想要角色在蹲下的时候自动聚气,一定时间后能够释放一个特殊的攻击技能。那么在角色蹲下的时候,我们需要记录一个聚气的时间。
我们在HeroIne中添加一个chargeTime_的变量,用来记录这个时间。同时,我们假象我们已经有了一个每帧可以更新的update()函数。那么我们就可以如下实现:
void Heroine::update(){
同时我们需要在角色开始蹲下的时候,重置这个时间,修改handleInput():
void Heroine::handleInput(Input input){
好,我们的需求修改完毕。我们只修改了2个函数,同时添加了一个chargeTIme_变量。我们的代码其实已经比较清晰了,但是我们这里添加的变量chargetTIme其实只是对蹲下状态有效的。那么下篇文章中看看我们如果处理,能有更好的整合效果把:)