FSM 有限状态机
一、概述
有限状态机(finite-state machine,缩写:FSM)又称有限状态自动机(finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
从历史上来说,有限状态机是一个被数学家用来解决问题的严格形式化的设备。在人工智能变成中,我们可以建 FSM 理解为:
一个有限状态机是一个设备,或是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得从一个状态变换到另一个状态,或者是促使一个输出或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。
二、分析
有限状态机,一般称作 FSM。常作为游戏 AI 设计的首选,尽管更专业的智能体结构越来越普及,但 FSM架构依然流行的原因如下
- 编程快速简单
有很多方法编码一个有限状态机,并且几乎乎所有的有限状态机实现都相当的简单。 - 易于调试
因为一个游戏智能体的行为被分解成简单的易于管理的块,如果一个智能体开始变得行动怪异,会通过对每一个状态增加跟踪代码来调试它。用这种方法,人工智能程序员可以很容易跟踪错误行为出现前的事件序列,并且采取相应的行动。 - 计算开销小
有限状态机几乎不占用珍贵的处理器时间,因为它们本质上遵守硬件编码的规则。除了 if-this-then-that 类型的思考处理之外,是不存在真正的“思考”的。 - 直觉性
人们总是自然地把事物思考为处在一种或另一种状态。并且我们也常常提到我们自己处在这样那样的状态中。有多少次你“使自己进入一种状态”或者发现自己处于“头脑的正确状态”。当然人类并不是像有限状态机一样工作,但是有时候我们发现在这种方式下考虑我们的行为是有用的,比如你处于口渴状态,你就会想去喝水。相似地,将一个游戏智能体的行为分解成一系列状态并且创建需要的规则来操作它们是相当容易的。出于同样的原因,有限状态机能够使你很容易地与非程序员(例如与游戏制作人和关卡设计师)来讨论你的人工智能的设计,能够更好地进行设计概念的沟通和交流。 - 灵活性
一个游戏智能体的有限状态机可以很容易地由程序员进行调整,来达到游戏设计者所要求的行为。同样通过增添新的状态和规则也很容易扩展智能体的行为的范围。此外,当你的人工智能技术提高了,你会发现有限状态机提供了一个坚固的支柱,使你可以用它来组合其他的技术,例如模糊逻辑和神经网络。
三、代码实现
下面我们实现一个简易的游戏状态机
- Hero 在没有攻击目标的时候是 Idle(待机) 状态
- 在发现怪物时会切换到 Attack(攻击) 状态
- 没有攻击目标 切回 Idle 状态
- 发现 Boss 或 自身血量低于 10 的时候 切换到 Escape (逃跑)状态
1、StateMachine
_curState
:当前状态,用于表示当前 Entity 所处的状态
_preState
:保存 Entity 的上一个状态,方便回溯
_globalState
:这是一个特殊的状态,可与 _curState
共存,引进全局状态的原因是为了解决一种优先级高的状态,可以保证在任意时刻切换到指定状态。比如模拟人生的游戏里面,有个人正在工作,突然想要上厕所,那么需要打断当前的状态(使用 _preState
记录)并切换到上厕所状态。在上完厕所后,又继续恢复工作(恢复上一个状态)。
public class StateMachine
{
private Entity _entity = null;
private State _globalState = null; // 全局状态
private State _preState = null; // 上一个状态
private State _curState = null; // 当前状态
public State CurState { get => _curState; }
private Dictionary<StateType, State> _dicState = null;
public void Init(Entity entity, List<string> listStateName)
{
_entity = entity;
_dicState = new Dictionary<StateType, State>();
if (null != listStateName)
{
int len = listStateName.Count;
for (int i = 0; i < len; i++)
{
string className = "FSM.AI.States." + listStateName[i];
Type stateType = Type.GetType(className);
State state = Activator.CreateInstance(stateType) as State;
state.Init(_entity);
AddState(state);
}
}
}
public void AddState(State state)
{
StateType stateType = state.StateType;
if (!_dicState.ContainsKey(stateType))
{
_dicState.Add(stateType, state);
}
else
{
Console.WriteLine(" State is already exist ! State : " + stateType);
}
}
public void Update()
{
if (null != _curState)
{
_curState.Execute();
}
if (null != _globalState)
{
_globalState.Execute();
}
}
public void ChangeState(StateType state, object data = null)
{
if (_dicState.ContainsKey(state))
{
_preState = _curState;
_curState = _dicState[state];
if (null != _preState)
{
_preState.Exit();
}
_curState.Enter(data);
}
else
{
Console.WriteLine(" State no exist ! " + _entity.GetEntityID() + " State " + state);
}
}
public void OnMessage(string messageID, object data = null) { }
}
2、State
Enter()
:进入状态时调用
Execute()
:每次执行时调用
Exit()
:退出状态时调用
using FSM.Entities;
namespace FSM.AI.States
{
public enum StateType
{
Idle = 0, // 待机
Attack = 1, // 攻击
Escape = 2, // 逃跑
}
public abstract class State
{
protected Entity _entity = null;
protected StateType _stateType = StateType.Idle;
public StateType StateType { get => _stateType; }
public virtual void Init(Entity entity, object data = null)
{
_entity = entity;
}
public virtual void Enter(object data = null) { }
public abstract void Execute();
public virtual void Exit() { }
public virtual void OnMessage(string msgID, object data = null) { }
}
}
3、Hero 相关状态
HeroIdleState
待机HeroAttackState
攻击HeroEscapeState
逃跑
using FSM.Entities;
using FSM.Manager;
using System.Collections.Generic;
namespace FSM.AI.States.Hero
{
public class HeroIdleState : State
{
private const int ESC_HP = 10;
public override void Execute()
{
Console.WriteLine($" {_entity.GetEntityType()} 待机中");
// 发现 BOSS 或者 血量小于 10 就逃跑
if (_entity.Data.hp < ESC_HP)
{
_entity.ChangeState(StateType.Escape);
}
List<Entity> listMonster = EntityManager.Instance.GetMonsters();
if (null != listMonster)
{
if (listMonster.Exists(it => it.GetEntityType() == EntityType.Boss))
{
_entity.ChangeState(StateType.Escape);
}
else
{
Entity monster = listMonster.Find(it => it.GetEntityType() == EntityType.Monster);
if (null != monster && !monster.IsDeath())
{
_entity.ChangeState(StateType.Attack);
}
}
}
}
}
public class HeroAttackState : State
{
public override void Init(Entity entity, object data = null)
{
base.Init(entity, data);
_stateType = StateType.Attack;
}
public override void Execute()
{
List<Entity> listMonster = EntityManager.Instance.GetMonsters();
if (null != listMonster)
{
Entity monster = listMonster.Find(it => it.GetEntityType() == EntityType.Monster);
if (null != monster)
{
_entity.Attack(monster);
}
else
{
_entity.ChangeState(StateType.Idle);
}
}
else
{
_entity.ChangeState(StateType.Idle);
}
}
}
public class HeroEscapeState : State
{
public override void Init(Entity entity, object data = null)
{
base.Init(entity, data);
_stateType = StateType.Escape;
}
public override void Execute()
{
Console.WriteLine(" Hero:敌人太强了 ! 跑路 !");
}
}
}
4、Entity
GetEntityID()
:获取 Entity 的唯一 ID
ChangeState()
:切换 Entity 状态
OnUpdate()
:定时刷新 Entity 状态,在游戏中一般是每帧刷新,Demo 中使用 0.5s 刷新一次
using FSM.AI;
using FSM.AI.States;
using FSM.Data;
using System;
namespace FSM.Entities
{
public enum EntityType
{
Hero = 0,
Monster = 1,
Boss = 2,
}
public abstract class Entity
{
protected long _iEntityIndex = 0;
protected StateMachine _stateMachine = null;
protected EntityData _data = null;
public EntityData Data { get => _data; }
public virtual void Init(object data) { }
public abstract string GetEntityID();
public StateType GetState()
{
if (null != _stateMachine && null != _stateMachine.CurState)
{
return _stateMachine.CurState.StateType;
}
return StateType.Idle;
}
public abstract EntityType GetEntityType();
public virtual void ChangeState(StateType state, Object data = null)
{
if (null != _stateMachine)
{
_stateMachine.ChangeState(state, data);
}
}
public virtual void OnUpdate()
{
if (null != _stateMachine)
{
_stateMachine.Update();
}
}
public virtual void Attack(Entity target) { }
public virtual void OnHurt(int deltaHp) { }
public abstract bool IsDeath();
public virtual void OnDestroy() { }
}
}
5、EntityManager
EntityManager
用于管理游戏中的所有 Entity
,包含 添加、移除 和 更新。亦可用于 Entity 间的互相通讯(下面的代码没有实现,只需简单添加 OnMessager(string entityID, object data = null){ }
,即可)。
using FSM.Entities;
using FSM.Interface;
using System;
using System.Collections.Generic;
namespace FSM.Manager
{
public class EntityManager : IManager
{
private static readonly Lazy<EntityManager> _instance =
new Lazy<EntityManager>(() => new EntityManager());
public static EntityManager Instance
{
get { return _instance.Value; }
}
private long _iEntityIndex = 0;
public long EntityIndex { get { return ++_iEntityIndex; } }
private Dictionary<string, Entity> _dicEntity = null;
private List<string> _listKey = null;
private EntityManager() { }
public void Init()
{
_dicEntity = new Dictionary<string, Entity>();
_listKey = new List<string>();
}
public void AddEntity(Entity entity)
{
string key = entity.GetEntityID();
if (!_dicEntity.ContainsKey(key))
{
_dicEntity.Add(key, entity);
_listKey.Add(key);
}
else
{
Console.WriteLine("Entity is exist id = " + key);
}
}
public void Update()
{
int len = _listKey.Count;
for (int i = 0; i < len; i++)
{
string key = _listKey[i];
Entity entity = _dicEntity[key];
if (!entity.IsDeath())
{
entity.OnUpdate();
}
// 移除死亡的对象
if (entity.IsDeath())
{
_listKey.RemoveAt(i);
len--;
i--;
_dicEntity.Remove(key);
}
}
}
public void RemoveEntity(string key)
{
if (_dicEntity.ContainsKey(key))
{
Entity entity = _dicEntity[key];
entity.OnDestroy();
_dicEntity.Remove(key);
_listKey.Remove(key);
}
}
public List<Entity> GetMonsters()
{
List<Entity> list = new List<Entity>();
int len = _listKey.Count;
for (int i = 0; i < len; i++)
{
string key = _listKey[i];
Entity entity = _dicEntity[key];
if (entity.GetEntityType() != EntityType.Hero)
{
list.Add(entity);
}
}
return list;
}
public Entity GetHero()
{
Entity hero = null;
int len = _listKey.Count;
for (int i = 0; i < len; i++)
{
string key = _listKey[i];
Entity entity = _dicEntity[key];
if (entity.GetEntityType() == EntityType.Hero)
{
hero = entity;
break;
}
}
return hero;
}
public void Clear()
{
_dicEntity.Clear();
_listKey.Clear();
}
}
}
6、Demo
Demo 流程:
- 创建 Hero 和 Monster
- Hero 和 Monster 互相攻击
- Monster 死亡后,Boss 登场
- Hero 逃逸
using FSM.Data;
using FSM.Entities;
using FSM.Manager;
using System;
using System.Threading;
namespace FSM
{
class Program
{
static void Main(string[] args)
{
EntityManager.Instance.Init();
// Add Hero
Hero hero = new Hero();
InitData initData = new InitData(100, 10, 10,
EntityManager.Instance.EntityIndex);
hero.Init(initData);
EntityManager.Instance.AddEntity(hero);
// Add Monster
Monster monster = new Monster();
InitData monsterInitData = new InitData(20, 2, 2,
EntityManager.Instance.EntityIndex);
monster.Init(monsterInitData);
EntityManager.Instance.AddEntity(monster);
bool createBoss = false;
while (!hero.IsDeath())
{
EntityManager.Instance.Update();
if (monster.IsDeath() && !createBoss)
{
createBoss = true;
Boss boss = new Boss();
InitData bossInitData = new InitData(1000, 50, 50,
EntityManager.Instance.EntityIndex);
boss.Init(bossInitData);
EntityManager.Instance.AddEntity(boss);
}
Thread.Sleep(500);
}
Console.WriteLine(" 游戏结束");
Console.ReadKey();
}
}
}
参考资料:
[1] 游戏人工智能编程案例精粹(修订版)
[2] Finite-state machine:https://en.wikipedia.org/wiki/Finite-state_machine
欢迎关注个人公众号,实时推送最新博文!