FSM 有限状态机
概念
- 如其名,在有限的状态下,管理状态的转移的一种设计模式
基本思路
pic- 如 状态a 要切换到 状态b,需要通过中介也就是状态管理机进行切换,而不是两种状态直接切换,降低了耦合
- 另外每种状态可能有不同的特效、动画或音效等...多种逻辑。所以可以把每个状态单独封装成一个 类 方便管理和维护
- 每个状态跳转之间有共性,所以可以抽象基类如:
- 状态进入
OnEnter
- 状态保持
OnStay
- 状态退出
OnExit
- 状态进入
-
状态管理机 只需要负责CRUD:
- 1.状态的注册
- 2.状态的注销
- 3.状态的改变
- 4.状态的查询
代码实现
以下代码在FsmBase.cs脚本 是每个状态的基类
- 首先定义一个枚举用来标识具体状态
public enum E_state {
Idle,
Jump,
Walk,
None
}
- 抽象共同特征
public class FsmBase {
/// <summary>
/// 状态的ID 用来标识每一个状态
/// </summary>
public E_state e_State { get; }
//初始化
public FsmBase (E_state _state) {
this.e_State = _state;
}
/// <summary>
/// 状态进入
/// </summary>
/// <param name="args">任意可变参数</param>
public virtual void OnEnter (params object[] args) { }
/// <summary>
/// 状态保持
/// </summary>
/// <param name="args">任意可变参数</param>
public virtual void OnStay (params object[] args) { }
/// <summary>
/// 状态退出
/// </summary>
/// <param name="args">任意可变参数</param>
public virtual void OnExit (params object[] args) { }
}
- 为了更好的通用性,再为FSMBase套个接口,其实就是模板,以后状态只需继承此模板类即可
/// <summary>
/// 状态模板 具体状态只需要继承此类即可
/// </summary>
/// <typeparam name="T">拥有此状态的对象</typeparam>
public class StateTemplates<T> : FsmBase where T : class {
public T m_owner; //状态的拥有者方便后期调用
public StateTemplates (E_state _state, T t) : base (_state) {
this.m_owner = t;
}
}
以下代码在FIniteStateMachine.cs脚本中 也就是👆的状态管理机
- 定义属性字段和初始化
//用个字典存所有状态
public Dictionary<E_state, FsmBase> m_allState;
/// <summary>
/// 上一个状态
/// </summary>
private FsmBase m_priousState;
/// <summary>
/// 当前状态
/// </summary>
private FsmBase m_curState;
//构造函数
public FiniteStateMachine () {
m_priousState = null;
m_curState = null;
m_allState = new Dictionary<E_state, FsmBase> ();
}
- 增
/// <summary>
/// 状态注册
/// </summary>
/// <param name="_fsmBase">想要被注册的状态</param>
public void RegistState (FsmBase _fsmBase) {
//没有这个状态就添加
if (!m_allState.ContainsKey (_fsmBase.e_State))
m_allState.Add (_fsmBase.e_State, _fsmBase);
}
- 删
//注销状态
public void UnRegistState (FsmBase _fsmBase) {
if (m_allState.ContainsKey (_fsmBase.e_State))
m_allState.Remove (_fsmBase.e_State);
}
- 改
/// <summary>
/// 改变状态
/// </summary>
/// <param name="_state">需要切换的下个状态</param>
/// <param name="args">可选参数</param>
public void ChangeState (E_state _state, params object[] args) {
//没有这个状态 或 就是当前状态 就退出
if (!m_allState.ContainsKey (_state) || _state == m_curState.e_State) return;
if (m_curState == null) return;
//本次状态退出
m_curState.OnExit (args);
//退出后把本次状态设为前一个状态
m_priousState = m_curState;
//根据想要更改的状态 去字典找 并设为当前状态
m_curState = m_allState[_state];
m_curState.OnEnter (args);
}
- 查
public E_state GetPriousState () {
if (m_priousState != null)
return m_priousState.e_State;
return E_state.None;
}
public E_state GetCurState () {
if (m_curState != null)
return m_curState.e_State;
return E_state.None;
}
- 最后一个更新和启动
public void UpdateState (params object[] args) {
if (m_curState != null)
m_curState.OnStay (args);
}
/// <summary>
/// 开始运作并进入一个状态
/// </summary>
/// <param name="_fsmBase">想要进入的状态</param>
/// <param name="args">可选参数</param>
public void Go (FsmBase _fsmBase, params object[] args) {
if (m_curState != null) return;
m_curState = _fsmBase;
m_curState.OnEnter (args);
}
接下来添加需要的状态 为了展示就定义两种状态🤭 以下代码在PlayerVariedState.cs脚本
- 需要控制状态的进出,只需要重写具体函数即可,不需要可不写
- 👆提供模板的方便之处就体现在这,这里
m_owner
就代表Player
using UnityEngine;
public class PlayerIdle : StateTemplates<Player> {
public PlayerIdle (E_state _state, Player _p) : base (_state, _p) { }
public override void OnEnter (params object[] args) {
// m_owner.GetComponent<MeshFilter> ()//获取本身组时只需用m_owner对象即可
Debug.Log ("进入待机状态");
}
public override void OnStay (params object[] args) {
Debug.Log ("保持待机状态");
//当前状态持续进行 ,
}
public override void OnExit (params object[] args) {
Debug.Log ("离开待机状态");
}
}
public class PlayerJump : StateTemplates<Player> {
public PlayerJump (E_state _state, Player _p) : base (_state, _p) { }
public override void OnEnter (params object[] args) {
Debug.Log ("进入跳状态");
}
public override void OnStay (params object[] args) {
Debug.Log ("保持跳状态");
}
public override void OnExit (params object[] args) {
Debug.Log ("离开跳状态");
}
}
最后进行测试😪 以下代码在Player.cs脚本
using UnityEngine;
public class Player : MonoBehaviour {
public FiniteStateMachine fsm;
private void Start () {
//造个状态机
fsm = new FiniteStateMachine ();
//实例化状态
var tmpIdle = new PlayerIdle (E_state.Idle, this);
var tmpJump = new PlayerJump (E_state.Jump, this);
//注册
fsm.RegistState (tmpIdle);
fsm.RegistState (tmpJump);
//启动机器
fsm.Go (tmpIdle);
}
private void Update () {
if (Input.GetKeyDown (KeyCode.Q))
fsm.ChangeState (E_state.Idle);
if (Input.GetKeyDown (KeyCode.W))
fsm.ChangeState (E_state.Jump);
fsm.UpdateState ();
}
}
-
将Player挂载一个游戏对象后 运行结果
pic2
总结
- 优点:
- 方便扩展,如果后期需添加新状态,只需继承基类即可
- 屏蔽了状态之间切换的繁琐性和复杂性(如状态增多之后)
- 每种状态不需要担心何时被调用,只需负责调用时的逻辑即可
- 符合开闭原则(对修改关闭对扩展开放)
最后:
鄙人学习笔记,仅供参考,如有不正,欢迎指正👀
网友评论