前言
在上Unity课的时候,老师介绍了一款可视化编程插件——PlayMaker。即使不需要技术人员,通过这个插件,游戏创作者也能创建出合适的游戏。这个插件内置了一个强大的状态机系统:用户可以定义不同的状态,从而实现对用户物体的操控。
这就引发了一个问题:状态这个东西怎么在代码中编写?了解完状态模式后,你会发现,这个模式就值65美元。
需求
我们要设计一个游戏NPC,这个NPC有以下状态:休息,走路,战斗。用一个简单的流程图来表示:

具体怎样触发状态转移这里不做阐述,可以利用回调函数,时间触发等一系列规则。这里我们只设计转移状态的接口。
状态模式
在在面向对象编程中,我们把许多东西看成是类,然后再去实现。同样的,状态也可以是类,而我们操作的对象,本例中是游戏NPC,也是类。利用继承、多态就能实现很多功能,状态模式的UML图如下:

该设计模式包含三个要点:
抽象类State:表示了其状态和行为;
ConcreteState:继承自State,实现接口;
Context:上下文,有表示状态的成员。
具体设计
首先设计我们的抽象类State,我们假设在每个状态下,NPC都会说话,因此需要设计一个talk接口。
class State {
public:
virtual void talk() = 0;
virtual ~State() = default;
};我们设计的三个状态继承自该接口。由于状态没有必要构造多个对象,我们可以用单例模式来写:
class WalkState : public State {
public:
static WalkState& getInstance()
{
static WalkState singleton;
return singleton;
}
void talk() override {
std::cout << "I am walking." << std::endl;
}
};
class RestState : public State {
public:
static RestState& getInstance()
{
static RestState singleton;
return singleton;
}
void talk() override {
std::cout << "zzz...." << std::endl;
}
};
class FightState : public State {
public:
static FightState& getInstance()
{
static FightState singleton;
return singleton;
}
void talk() override {
std::cout << "I am fighting." << std::endl;
}
};再来实现我们的NPC类:
class NPC {
public:
void setState(State& state) {
nowState = &state;
}
const State* getState() const {
return nowState;
}
void talk() {
if (nowState) {
nowState->talk();
}
}
private:
State* nowState = nullptr;
};尝试在main函数中调用:
int main()
{
NPC somebody;
somebody.setState(WalkState::getInstance());
somebody.talk();
somebody.setState(RestState::getInstance());
somebody.talk();
somebody.setState(FightState::getInstance());
somebody.talk();
}输出结果如下:
I am walking.
zzz....
I am fighting.成功实现了我们的需求。
优点
假设以后我们要为这个NPC添加新状态,只需要编写新状态即可,不需要更改NPC类的内部实现。
评论区