在本文 ,笔者就FSM跟大家简单聊聊,希望对这个东东陌生的小伙伴能有那么一丢丢帮助罢~
一. 为什么FSM?
FSM ,如其名有限状态机,就是说啊这是一个可以枚举出有限个状态,并且这些个状态在特定条件下能够来回切换的机器。
在小游戏里面出现的简单 AI 体验:怪物巡逻、怪物追击、目标丢失继续巡逻、发生战斗血量不足逃跑、发生战斗血量为0死亡
等等,大多出自它手啦!
另外FSM的理念又似乎随处可见,细心的你有没有在某一刻发现 Unity 的 Animator 其实就是一个有限状态机呢?
也许了解了FSM 会不会更好的理解这个Animator组件了呢?
二. FSM与设计模式
细节依赖抽象,抽象不依赖细节,基于抽象编程,让框架先跑起来。
FSM 把上面这句话演绎的淋漓尽致,俨然已经算的上是一个简单的框架了,只要遵循他的规则,你只要写细节实现就行,其他事情全部帮你驱动。
FSM 跟 Switch case 语法做的事一样一样的,类比于 Switch 就是将case中的逻辑封装到各个State类型中了。
那为啥这样做呢?
答案很简单,就是为了方便扩展,如果后期需要加入新状态,只需要继承基类,添加实现就好,不用修改原来的代码,也不用担心什么时候调用啦,这就叫开闭原则(开闭原则:对修改关闭对扩展开放)。
Tips:FSM 还是设计模式里的状态模式理念的集大成哦。
三. CRUD 和 生命周期
想要盘一个自己的 有限状态机,你需要对标题上的这两个概念有所了解:
- CRUD :增删改查,在FSM里我们需要管理状态,拿什么管理?或者说怎么个思路管理?CRUD呗。
- 生命周期: 抽象一套生命周期,在状态机里面先跑起来,这样所有子类只要在你的 FSM 框架内,就能无需关注何时调用(驱动/触发)而去实现细节了。
关于FSM 的生命周期函数(极简版):
- OnEnter() //当状态进入时刻
- OnUpdate() //当前状态持续进行中 ,状态转换条件往往在这里进行判断。
- OnExit() //当状态退出时触发
四. 极简FSM
下面提供一个 FSM, 代码量是少了些,但有些时候少就是多。也算是抛砖引玉吧!
FSM
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace zFrame.FSM
{
public class FSM
{
private List<IState> m_list; //状态机维护的一组状态
public IState CurrentState{get; private set; } //当前状态
public FSM()
{
m_list = new List<IState>();
}
public void AddState(IState _state)// 添加指定的状态
{
IState _tmpState = GetState(_state.GetState());
if (_tmpState == null)
{
m_list.Add(_state);
}
else
{
Debug.LogWarningFormat("FSMSystem(容错):该状态【{0}】已经被添加!", _state.GetState().ToString());
}
}
public void RemoveState(IState _state) //删除状态
{
IState _tmpState = GetState(_state.GetState());
if (_tmpState != null)
{
m_list.Remove(_tmpState);
}
else
{
Debug.LogWarningFormat("FSMSystem(容错):该状态【{0}】已经被移除!", _state.GetState().ToString());
}
}
public IState GetState(string state)//获取相应状态
{
foreach (IState _state in m_list) //遍历List里面所有状态取出相应的
{
if (_state.GetState() == state)
{
return _state;
}
}
return null;
}
/// <summary>
/// 状态机状态翻转
/// </summary>
/// <param name="state">指定状态机</param>
/// <returns>执行结果</returns>
public void ChangeState(string state)
{
IState _tmpState = GetState(state); //要改变的状态不存在
if (_tmpState == null)
{
Debug.LogWarningFormat("FSMSystem(容错):该状态【{0}】不存在于状态机中!", state);
}
if (CurrentState != null) //当前状态不为空
{
CurrentState.OnExit();
}
CurrentState = _tmpState; //缓存为当前状态
CurrentState.OnEnter(); //触发当前状态的OnEnter
}
public void Update()// 更新状态机状态
{
if (CurrentState != null)
{
CurrentState.OnUpdate();
}
}
public void RemoveAllState() //移除所有状态
{
if (CurrentState != null)
{
CurrentState.OnExit();
CurrentState = null;
}
m_list.Clear();
}
}
}
IState
namespace zFrame.FSM
{
public interface IState //抽象出的公共行为
{
/// <summary>
/// 状态进入
/// </summary>
void OnEnter();
/// <summary>
/// 状态更新
/// </summary>
void OnUpdate();
/// <summary>
/// 状态退出
/// </summary>
void OnExit();
/// <summary>
/// 获得状态
/// </summary>
string GetState();
}
}
五. 怎么使用这个FSM?
- 声明state名称的常量若干。
- MonoBehaviours Start方法中 new 一个 FSM。
- FSM 实例中 AddState(new XXState())。
- MonoBehaviours Update方法中 调 FSM 实例 的Update 方法。
- 状态转换 调用 FSM实例的 ChangeState() 方法。
Tips: FSM在哪 new 好点?
- 如果FSM算的上一个Mananger的话,他需要一个Manager来持有,也就是Manager of Manager (高内聚)。
- 如果这个 FSM 是一个 AI 的大脑,那就让这个AI 身上的主脚本持有,减少其他不必要的访问(迪米特法则)。
写到最后:
文章最后贴的代码只是 FSM 理念的一种实现,相较于常见实现,可以明显发现少了 一个 Transition理念。可类比于 Animator 或者 下面 贴的扩展阅读中的 FSM。
限于笔者水平,难免有遗漏,欢迎留言斧正共同进步。