状态模式
“允许一个对象在其内部状态改变时改变自身的行为。对象看起来好像是在修改自己的类。”
什么是状态模式
状态模式是一种行为设计模式,它允许对象在内部状态发生变化时改变自己的行为。通过将不同状态的行为封装成独立的类,这样在切换状态时可以更灵活地替换当前状态对象,从而实现行为的变化。
为什么要用状态模式
在游戏开发等场景中,状态模式非常适合用来管理角色、敌人或物体的复杂状态变化。通过状态模式,可以避免将大量条件语句(if-else
或 switch
)集中在一个类中,代码更清晰、易维护,也方便扩展和调试。
状态模式的实现细节——以编写拥有多状态的游戏玩家为例
1. 一个状态接口
首先,为各个状态定义一个通用接口,以规范每个状态的行为。每个具体状态类都实现这个接口。
代码例子
#include <iostream>
#include <memory>
class PlayerState {
public:
virtual ~PlayerState() = default;
virtual void enterState() = 0; // 进入状态行为
virtual void exitState() = 0; // 退出状态行为
virtual void handleInput() = 0; // 处理输入
};
class Player;
2. 为每一个状态定义一个类
每个状态类实现具体的状态行为。在此例子中,我们定义两个状态:IdleState
(空闲状态)和 RunState
(跑动状态)。
代码例子
class IdleState : public PlayerState {
public:
void enterState() override {
std::cout << "Entering Idle State." << std::endl;
}
void exitState() override {
std::cout << "Exiting Idle State." << std::endl;
}
void handleInput() override {
std::cout << "Idle: Waiting for player input..." << std::endl;
}
};
class RunState : public PlayerState {
public:
void enterState() override {
std::cout << "Entering Run State." << std::endl;
}
void exitState() override {
std::cout << "Exiting Run State." << std::endl;
}
void handleInput() override {
std::cout << "Run: Handling player input for running..." << std::endl;
}
};
3. 状态委托
将状态委托给玩家对象,玩家类可以持有当前状态并在状态切换时更改它。
代码例子
class Player {
private:
std::unique_ptr<PlayerState> currentState;
public:
void changeState(std::unique_ptr<PlayerState> newState) {
if (currentState) {
currentState->exitState();
}
currentState = std::move(newState);
currentState->enterState();
}
void handleInput() {
if (currentState) {
currentState->handleInput();
}
}
};
4. 状态对象应该放在哪里
有两种放置状态对象的方法:静态对象和实例化对象。
静态对象
适用于状态类没有数据成员且只需唯一实例的情况。
优点:高效,简单
缺点:无法存储数据成员,只能存在唯一实例
实例化状态
适用于需要存储数据成员或可能有多个实例的情况。状态切换时要注意内存管理,以防碎片化。
5. 进入状态和退出状态的行为
为每个状态定义进入和退出方法,在状态切换时调用。

// 示例:
auto idleState = std::make_unique<IdleState>();
auto runState = std::make_unique<RunState>();
Player player;
player.changeState(std::move(idleState)); // 切换到空闲状态
player.handleInput();
player.changeState(std::move(runState)); // 切换到跑动状态
player.handleInput();
有限状态机的优缺点和它的限制
优点:易于管理、可扩展性高,代码更简洁。
缺点:状态较多时,可能产生大量状态类,增加维护成本。
限制:有限状态机适用于状态之间转换有限的情况,若状态间关系复杂则适合使用行为树或规划系统。
状态机扩展
并发状态机
将不同状态并行执行,实现多状态组合效果。适合需要并发操作的复杂逻辑。
代码例子
// 可以在Player类中同时持有多个状态对象,并分别调用它们的handleInput()方法。
层次状态机
通过继承的方式建立父子状态,子状态无法处理的事件可传递至父状态,复用相同行为。
代码例子
// 可以通过继承构建一个父状态类,子状态可复用父类行为
class BaseState : public PlayerState {
// 基础行为实现
};
class SpecialState : public BaseState {
// 特殊行为实现,继承BaseState的行为
};
下推自动机
用于记录上一个状态,以便完成当前状态后回到先前状态。
代码例子
#include <stack>
class PlayerWithStateStack {
std::stack<std::unique_ptr<PlayerState>> stateStack;
public:
void pushState(std::unique_ptr<PlayerState> newState) {
if (!stateStack.empty()) {
stateStack.top()->exitState();
}
stateStack.push(std::move(newState));
stateStack.top()->enterState();
}
void popState() {
if (!stateStack.empty()) {
stateStack.top()->exitState();
stateStack.pop();
}
if (!stateStack.empty()) {
stateStack.top()->enterState();
}
}
void handleInput() {
if (!stateStack.empty()) {
stateStack.top()->handleInput();
}
}
};
有限状态机的适用情况
- 游戏角色、NPC、敌人等行为管理:如闲置、攻击、受伤、逃跑等状态。
- 用户接口交互状态:如按钮状态、加载状态、提交状态等。
- 物体的生命周期管理:如武器、道具的不同状态管理。
更复杂的游戏AI——行为树和规划系统
对于复杂的AI,有限状态机可能不够用,可以使用行为树(Behavior Tree)或规划系统(Planner)实现更细致的决策逻辑。
总结
状态模式通过将不同状态封装为独立对象,使对象行为随着状态变化而改变,避免了大量条件判断,代码更清晰。它适用于各种具有有限状态的系统,但在状态关系较复杂的情况下,可能需要借助更复杂的AI系统。