一.基本概念
有限状态机(Finite State Machine,简称FSM)是一种数学模型,用于表示系统或组件在有限个状态之间进行切换和响应特定事件的行为。它由一组状态、状态之间的转移规则和触发这些转移的事件组成。
二.有限状态机的特点
- 状态总数有限: 任一时刻,系统只处在一种状态之中。
- 状态转换有规则: 某种条件下,系统会从一种状态转变到另一种状态。
- 行为可预测: 系统状态变化是有规则的,可以通过状态转移表明确知道在接收到特定事件时系统会如何反应。
三.应用
有限状态机在计算机科学中有广泛的应用,在游戏开发中常用于角色的简单AI逻辑,角色的动画播放,动画切换等。以下我们将使用一个有限状态机模板来执行一段简单的AI逻辑。
四.代码实现
- 状态机
#pragma once
#include<memory>
#include<unordered_map>
#include<string>
#include<iostream>
#include<cstdlib>
#include<ctime>
template<typename T> class IState;//前向声明
template<typename T>
class StateMachine
{
private:
//保存状态map
std::unordered_map<std::string, std::shared_ptr<IState<T>>> stateMap;
//当前执行中的状态
std::shared_ptr<IState<T>> currentState;
std::string currentStateName;
public:
//注册状态
void registerState(const std::string& stateName, const std::shared_ptr<IState<T>> state);
//切换状态
void changeState(const std::string& stateName);
void update(float deltaTime);
};
template<typename T>
void StateMachine<T>::registerState(const std::string& stateName, const std::shared_ptr<IState<T>> state)
{
stateMap.emplace(stateName, state);
}
template<typename T>
void StateMachine<T>::changeState(const std::string& stateName)
{
//判断避免重复切换同一个状态
if (stateName == currentStateName)
{
std::cout << "Already in state: " << stateName << std::endl;
return;
}
//判断状态是否已注册
auto it = stateMap.find(stateName);
if (it == stateMap.end())
{
std::cout << "State " << stateName << " not found!\n";
return;
}
if (currentState) currentState->onExit();//退出上一个状态
currentStateName = stateName;
currentState = it->second;
currentState->onEnter();//进入新状态
}
template<typename T>
void StateMachine<T>::update(float deltaTime)
{
if (currentState) currentState->onUpdate();
}
- 状态基类
#pragma once
#include<memory>
#include"StateMachine.h"
template<typename T>
class IState
{
protected:
//持有一个状态机指针
std::shared_ptr<StateMachine<T>> machine;
//状态的拥有者指针
T* target;
//测试计数器
short count;
//返回一个随机数用于测试
size_t getRandomNumber()
{
std::srand(std::time(0));
return std::rand();
}
public:
IState(std::shared_ptr<StateMachine<T>> _machine,T* _target,short _count = 0)
:machine(_machine), target(_target), count(_count) {};
virtual ~IState() = default;
//进入状态
virtual void onEnter() = 0;
//退出状态
virtual void onExit() = 0;
//状态更新
virtual void onUpdate() = 0;
};
- 具体状态类 IdleState
#pragma once
#include"IState.h"
#include"Soldier.h"
class IdleState :public IState<Soldier>
{
public:
IdleState(std::shared_ptr<StateMachine<Soldier>> _machine, Soldier* _target,short _count = 0)
:IState<Soldier>(_machine,_target,_count) {};
void onEnter() override
{
count = 0;
std::cout << "Enter Idle state!\n";
}
void onExit() override
{
std::cout << "Exit Idle state!\n";
}
void onUpdate() override
{
target->idle();
count++;
if (count >= 3)
machine->changeState("Patrol");
}
};
- 具体状态类 PatrolState
#pragma once
#include"IState.h"
#include"Soldier.h"
class PatrolState :public IState<Soldier>
{
public:
PatrolState(std::shared_ptr<StateMachine<Soldier>> _machine, Soldier* _target,short _count = 0)
:IState<Soldier>(_machine, _target, _count) {};
void onEnter() override
{
std::cout << "Enter Patrol state!\n";
}
void onExit() override
{
std::cout << "Exit Patrol state!\n";
}
void onUpdate() override
{
target->patrol();
//巡逻中随机切换到攻击状态 模拟遇见地方单位
if (getRandomNumber() % 2 == 0)
machine->changeState("Attack");
}
};
- 具体状态类 AttackState
#pragma once
#include"IState.h"
#include"Soldier.h"
class AttackState :public IState<Soldier>
{
public:
AttackState(std::shared_ptr<StateMachine<Soldier>> _machine, Soldier* _target, short _count = 0)
:IState<Soldier>(_machine,_target,_count){};
void onEnter() override
{
std::cout << "Enter attack state!\n";
}
void onExit() override
{
std::cout << "Exit attack state!\n";
}
void onUpdate() override
{
target->attack();
count++;
if (count >= 3) machine->changeState("Patrol");//攻击三次后切换到巡逻状态
}
};
- 测试类状态持有者
#pragma once
#include<memory>
//属性结构体
struct Attribute
{
float hp;//血量
float aggressivity;//攻击力
float walkSpeed;//移动速度
};
template<typename T> class StateMachine;//前向声明
class Soldier
{
private:
Attribute* attrPtr;
protected:
public:
Soldier(float hp, float aggressivity, float walkSpeed);
~Soldier(){delete attrPtr;}
std::shared_ptr<StateMachine<Soldier>> machinePtr;
void attack();
void patrol();
void idle();
void update(float dt);
};
#include "Soldier.h"
#include"IdleState.h"
#include"AttackState.h"
#include"PatrolState.h"
Soldier::Soldier(float hp, float aggressivity, float walkSpeed)
{
//初始基础属性
attrPtr = new Attribute;
attrPtr->hp = hp;
attrPtr->aggressivity = aggressivity;
attrPtr->walkSpeed = walkSpeed;
//初始化状态机
machinePtr = std::make_shared<StateMachine<Soldier>>();
//注册状态
machinePtr->registerState("Idle", std::make_shared<IdleState>(machinePtr, this));
machinePtr->registerState("Patrol", std::make_shared<PatrolState>(machinePtr, this));
machinePtr->registerState("Attack", std::make_shared<AttackState>(machinePtr, this));
machinePtr->changeState("Idle");//初始先切换到闲置状态
}
void Soldier::attack()
{
std::cout << "烈鸟爆蛋王八拳!\n";
};
void Soldier::patrol()
{
std::cout << "巡逻中.....\n";
}
void Soldier::idle()
{
std::cout << "晚上吃啥?\n";
}
void Soldier::update(float dt)
{
machinePtr->update(dt);
}
- 在mian函数中启动测试
#include<thread>
#include"Soldier.h"
int main()
{
Soldier soldier(100,100,100);
while (true)
{
soldier.update(1);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
- 执行结果
可以看到状态机按照我们预先制定的规则进行状态切换,在对应的状态中处理对应的逻辑。