7.3 枚举和分支
一个问题是,Heroine类有一些布尔类型的成员变量:isJumping_和isDucking_,但是这两个变量不应该同时为true。当你有一系列的标记成员变量,而它们只能有且仅有一个为true时,这表明我们需要把它们定义成枚举(enum)。
在这个例子当中,我们的有限状态机的每一个状态可以用一个枚举来表示,所以,让我们定义以下枚举:
enum State
{
STATE_STANDING,
STATE_JUMPING,
STATE_DUCKING,
STATE_DIVING
};
这里没有大量的标志位,Heroine类只有一个state_成员。我们也需要调换分支语句的顺序。在前面的代码中,我们先判断输入事件,然后才是状态。那种代码可以让我们集中处理每一个按键相关的逻辑,但是,它也让每一种状态的处理代码变得很乱。我们想把它们放在一起来处理,因此,我们先判断状态。代码如下:
void Heroine::handleInput(Input input)
{
switch (state_)
{
case STATE_STANDING:
if (input == PRESS_B)
{
state_ = STATE_JUMPING;
yVelocity_ = JUMP_VELOCITY;
setGraphics(IMAGE_JUMP);
}
else if (input == PRESS_DOWN)
{
state_ = STATE_DUCKING;
setGraphics(IMAGE_DUCK);
}
break;
// Other states...
}
}
我们可以像下面设置其他状态:
void Heroine::handleInput(Input input)
{
switch (state_)
{
// Standing state...
case STATE_JUMPING:
if (input == PRESS_DOWN)
{
state_ = STATE_DIVING;
setGraphics(IMAGE_DIVE);
}
break;
case STATE_DUCKING:
if (input == RELEASE_DOWN)
{
state_ = STATE_STANDING;
setGraphics(IMAGE_STAND);
}
break;
}
}
这样看起来虽然很普通,但是它却是对前面的代码的一个提升。我们仍然有一些条件分支语句,但是我们简化了状态的处理。所有处理单个状态的代码都集中在一起了。这是实现状态机最简单的方法,而且在某些情况下,这样做也挺好的。
重要的是,我们的女主角再也不可能处于一个无效的状态了。通过布尔值标识,会存在一些没有意义的值。但是,使用枚举,则每一个枚举值都是有意义的。
你的问题可能也会超过此方案能解决的范围。比如,我们想在主角下蹲躲避的时候“蓄能”,然后等蓄满能量之后可以释放出一个特殊的技能。那么,当主角处于躲避状态的时候,我们需要添加一个变量来记录蓄能时间。
如果你猜这是更新方法模式,那么恭喜你,你猜中了!
我们可以在Heroine类中添加一个chargeTime_成员来记录主角蓄能的时间长短。假设,我们已经有一个update()方法了,并且这个方法会在每一帧被调用。在那里,我们可以使用如下代码片断能记录蓄能的时间:
void Heroine::update()
{
if (state_ == STATE_DUCKING)
{
chargeTime_++;
if (chargeTime_ > MAX_CHARGE)
{
superBomb();
}
}
}
我们需要在主角躲避的时候重置这个蓄能时间,所以,我们还需要修改handleInput()方法:
void Heroine::handleInput(Input input)
{
switch (state_)
{
case STATE_STANDING:
if (input == PRESS_DOWN)
{
state_ = STATE_DUCKING;
chargeTime_ = 0;
setGraphics(IMAGE_DUCK);
}
// Handle other inputs...
break;
// Other states...
}
}
总之,为了添加蓄能攻击,我们不得不修改两个方法,并且添加一个chargeTime_成员变量给主角,尽管这个成员变量只有在主角处于躲避状态的时候才有效。其实我们真正想要的是把所有这些和与之相关的数据和代码封装起来。接下来,我们介绍GoF的状态模式来解决这个问题。