Cocos2d-x游戏开发之有限状态机(FSM)
有限状态机概述
基本上所有的软件都可看作有限状态机(finite-state machine,FSM)。它是一个有向图,由一组节点和一组相应的转移函数构成。通俗来讲,有限状态机是事件驱动系统的一种模型,该模型包含有限数量的状态、若干输入以及状态之间转换的规则。在某一时刻,有限状态机存在一个或一组当前状态。当有限状态机接收到输入事件时,会依据转换规则将当前状态转换为新的状态。正是由于状态、输入和转换规则这三个元素的组合,使得有限状态机具备了独特的行为特点。在游戏开发中,有限状态机常被用于实现人工智能的决策过程,控制游戏对象的行为。
最简单的状态机
上述解释可能有些抽象,多数文章会用“门”或“锁”的例子来说明状态机,这里举一个不同的例子:有一个名为“猴子”的NPC,它会在指定区域内行走,有时会停留,当行走到区域边界时会自行转向(图1)。我们将状态机的转换规则函数列成表格(图2),并根据这些规则给出这个简单状态机的C++实现:
#ifndef MONKEY_H_
#define MONKEY_H_
#include "cocos2d.h"
USING_NS_CC;
#define MAX_STOP_TIME 10
#define MAX_WALK_TIME 20
#define MAX_WALK_DIST 100
enum MonkeyState
{
stSTOP,
stWALK,
stTURN
};
class Monkey : public Node
{
public:
Monkey()
{
log("Monkey()");
}
CREATE_FUNC(Monkey);
virtual bool init()
{
_curPos = 0;
_step = 1;
changeState(stSTOP);
this->scheduleUpdate();
return true;
}
void changeState(MonkeyState newState)
{
_curState = newState;
_curTime = time(0);
}
void stop()
{
cocos2d::log("stop()");
}
void walk()
{
_curPos += _step;
cocos2d::log("walk(): pos=%d", _curPos);
}
void turn()
{
_step *= -1;
cocos2d::log("turn(): step=%d", _step);
}
void update(float dt)
{
switch (_curState) {
case stSTOP:
if (isStopTimeout()) {
changeState(stWALK);
walk();
}
break;
case stWALK:
walk();
if (isWalkOutBorder()) {
changeState(stTURN);
turn();
} else if (isWalkTimeout()) {
changeState(stSTOP);
stop();
}
break;
case stTURN:
changeState(stWALK);
walk();
break;
}
}
private:
MonkeyState _curState;
time_t _curTime;
int _curPos;
int _step;
public:
bool isStopTimeout()
{
return (time(0) - _curTime > MAX_STOP_TIME);
}
bool isWalkTimeout()
{
return (time(0) - _curTime > MAX_WALK_TIME);
}
bool isWalkOutBorder()
{
return (_curPos > MAX_WALK_DIST || _curPos < -MAX_WALK_DIST);
}
};
#endif // MONKEY_H_
坏代码的问题
显然,如果将上述代码补充完整,它能够正常工作。但这里已经能察觉到“坏代码”的隐患。代码中长长的条件判断语句会随着状态数量的增加而变得更长。每新增一个状态,就需要在冗长的条件判断语句中仔细查找和修改。当条件语句的规模增长到需要多人合作完成时,会引发严重的维护和调试问题。另外,对于编译型语言而言,一个具有N个状态的有限状态机要查找一个正确的状态,平均需要进行N/2次判断。
在上述状态机中,对象在不同状态下表现出不同的行为特征,这些不同的行为特征除了有类似的形式化接口外,基本没有其他联系。因此,我们很自然地会想到使用状态模式,利用状态模式来维护状态,实现对象与状态维护的分离。