本节书摘来异步社区《游戏编程模式》一书中的第7章,第7.5节,作者: 【美】Robert Nystrom (尼斯卓姆) 译者: 赵卫兵 , 许新星 , 姜召阳 , 陈侃 , 屈光辉 , 郑炯彬 责编: 陈冀康,更多章节内容可以访问云栖社区“异步社区”公众号查看。
7.5 状态对象应该放在哪里呢
我这里忽略了一些细节。为了修改一个状态,我们需要给state_指针赋值为一个新的状态,但是这个新的状态对象要从哪里来呢?我们之前的枚举方法是定义一些数字。但是,现在我们的状态是类,我们需要获取这些类的实例。通常来说,有两种实现方法。
7.5.1 静态状态
如果一个状态对象没有任何数据成员,那么它的唯一数据成员便是虚表指针了。那样的话,我们就没有必要创建此状态的多个实例了,因为它们的每一个实例都是相同的。
在那种情况下,我们可以定义一个静态实例。即使你有一系列的FSM在同时运转,所有的状态机也能同时指向这一个唯一的实例。
如果你的状态类没有任何数据成员,并且只有一个虚函数方法。那么我们还可以进一步简化此模式。我们可以使用一个普通的状态函数来替换状态类。这样的话,我们的state_变量就变成一个状态函数指针。
这个就是享元模式。(第3章)
你把静态方法放置在哪里,这个由你自己来决定。如果没有任何特殊原因的话,我们可以把它放置到基类状态类中:
class HeroineState
{
public:
static StandingState standing;
static DuckingState ducking;
static JumpingState jumping;
static DivingState diving;
// Other code...
};
每一个静态成员变量都是对应状态类的一个实例。如果我们想让主角跳跃,那么站立状态应该是这样子:
if (input == PRESS_B)
{
heroine.state_ = &HeroineState::jumping;
heroine.setGraphics(IMAGE_JUMP);
}
7.5.2 实例化状态
有时候上面的方法可能不行。一个静态状态对于躲避状态而言是行不通的。因为它有一个chargeTime_成员变量,所以这个具体取决于每一个躲避状态下的主角类。如果我们的游戏里面只有一个主角的话,那么定义一个静态类也是没有什么问题的。但是,如果我们想加入多个玩家,那么此方法就行不通了。
当你为状态实例动态分配空间时,你不得不考虑碎片化问题了。对象池模式(第19章)可以帮助到你。
在那种情况下,我们不得不在状态切换的时候动态地创建一个躲避状态实例。这样,我们的有限状态机就拥有了它自己的实例。当然,如果我们又动态分配了一个新的状态实例,则要负责清理老的状态实例。这里必须相当小心,因为修改状态的函数是在当前状态里面,所以我们需要小心地处理删除的顺序。
另外,我们也可以选择在HeroineState类中的handleInput()方法里面可选地返回一个新的状态。当这个状态返回的时候,主角将会删除老的状态并切换到这个新的状态,如下所示:
void Heroine::handleInput(Input input)
{
HeroineState* state = state_−>handleInput(
*this, input);
if (state != NULL)
{
delete state_;
state_ = state;
}
}
那样的话,我们只有在从handleInput方法返回的时候才有可能去删除前面的状态对象。现在,站立状态可以通过创建一个躲避状态的实例来切换状态了。
HeroineState* StandingState::handleInput(
Heroine& heroine, Input input)
{
if (input == PRESS_DOWN)
{
// Other code...
return new DuckingState();
}
// Stay in this state.
return NULL;
}
通常情况下,我倾向于使用静态状态。因为它们不会占用太多的CPU和内存资源。