7.8 并发状态机
我们决定给我们的主角添加持枪功能。当她持枪的时候,她仍然可以:跑、跳和躲避等。但是,她也需要能够在这些状态过程中开火。
如果你执着于传统的有限状态机,那我们可能需要把之前的状态加倍。对于每一个已经存在的状态,我们需要定义另一个状态,它做的事情也差不多,不过就是多了持枪的操作。比如站立状态和站立开火状态,跳跃状态和跳跃开火状态等。
如果我们添加更多的武器种类,那么这个状态数量将会急剧增加。而且不仅仅是增加了大量的状态类实例,它还会增加大量的冗余,实际上带不带枪的状态仅有是否包含开火代码的区别而已。
这里的问题是,我们把两种状态杂合在一起了。我们把两种不同的状态硬塞到一个状态机里面去了。为所有可能出现的组合建模,我们可能需要为每一种状态准备一组状态。解决方法比较直观,就是分开成两个状态机。
如果我们需要为主角定义n种状态和m种它能够携带的武器状态,如果使用一个状态机来表示,那么我们需要n×m个状态。而如果使用两个状态机,那么状态组合仅是n+m。
首先我们可以保留原有的状态机的代码和功能不管它。接下来,我们定义一个单独的状态机,用来处理主角携带的武器。现在,我们的主角会有两个状态索引,其中一个看起来如下所示:
为了便于示例说明,我们这里使用了完整的状态模式来处理女主角的装备变化。事实上,由于装备目前只有两个状态,我们完全可以只使用一个布尔值变量来替代。
class Heroine
{
// Other code...
private:
HeroineState* state_;
HeroineState* equipment_;
};
当主角派发输入事件给状态类时,需要给两种状态都派发一下。
void Heroine::handleInput(Input input)
{
state_->handleInput(*this, input);
equipment_->handleInput(*this, input);
}
这样每一个状态机都可以响应输入事件并以此切换状态而不用考虑其他状态机的实现细节。当两个状态没什么关系的时候,这种方法工作得很好。
功能更加完备的系统可能会让一个状态机来处理输入,以便另外一个状态机不会接收到输入。这样将能防止两个状态机对同一输入进行错误的响应。
在实际中,你可能会发现你需要对某些状态处理进行干预。比如,如果主角不能够在跳跃的过程中开火,或者她在装备武器的时候不能俯冲。为了处理这种情况,在代码里面,对于每一个状态,你可能需要做一些简单的if判断并做出特殊处理。虽然这可能不是最好的解决方案,但是至少它可以完成任务。