• Unity3d 商业工程中的 FSM 有限状态机 实现代码


    一、有限状态机介绍

    有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。

    状态机有 3 个组成部分:状态、事件、动作

    状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
    事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
    动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

    FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。

    二、有限状态机的设计

    设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;

    主要分成了4个类文件:

    1.状态机基类 FsmBase.cs

    包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。

    FsmBase.cs 代码

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace Myh
    7. {
    8. ///
    9. /// 状态机基类
    10. ///
    11. public abstract class FsmBase
    12. {
    13. //状态机编号
    14. public int FsmId { private set; get; }
    15. //当前状态的类型
    16. public sbyte CurrStateType;
    17. public FsmBase(int fsmId)
    18. {
    19. FsmId = fsmId;
    20. }
    21. //关闭状态机
    22. public abstract void ShutDown();
    23. }
    24. }

    2.状态机类 Fsm.cs

    状态机的具体类,继承了FsmBase类;内部实现了所有具体状态的管理、添加、更新、切换等。

    Fsm.cs实现代码

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace Myh
    7. {
    8. //拥有者,拥有这个状态的类型
    9. public class Fsm<T> : FsmBase where T : class
    10. {
    11. //拥有者
    12. public T Owner { private set; get; }
    13. //当前状态
    14. private FsmState m_CurrState;
    15. //状态字典
    16. private Dictionary<sbyte, FsmState> m_dicState;
    17. //参数字典(??切换状态的时候还传参数??)
    18. private Dictionary<string, VariableBase> m_dicParam;
    19. /*
    20. * 构造函数 需要以下参数
    21. * fsmId:状态机的id
    22. * owner:拥有者
    23. * states:状态机里的所有状态
    24. */
    25. public Fsm(int fsmId, T owner, FsmState[] states) : base(fsmId)
    26. {
    27. m_dicState = new Dictionary<sbyte, FsmState>();
    28. m_dicParam = new Dictionary<string, VariableBase>();
    29. Owner = owner;
    30. //把状态放入字典
    31. int len = states.Length;
    32. for (int i = 0; i < len; ++i)
    33. {
    34. FsmState state = states[i];
    35. if (null != state)
    36. {
    37. state.CurrFsm = this;
    38. }
    39. m_dicState[(sbyte)i] = state;
    40. }
    41. CurrStateType = -1;
    42. }
    43. //获取状态 by 状态类型
    44. public FsmState GetState(sbyte stateType)
    45. {
    46. FsmState state = null;
    47. m_dicState.TryGetValue(stateType,out state);
    48. return state;
    49. }
    50. //状态机更新
    51. public void OnUpdate()
    52. {
    53. if (null != m_CurrState)
    54. {
    55. m_CurrState.OnUpdate();
    56. }
    57. }
    58. //状态机切换 by newStateType
    59. public void ChangeState(sbyte newState)
    60. {
    61. //如果两个状态一致,不再进入
    62. if (CurrStateType == newState)
    63. return;
    64. //先让当前状态执行OnLeave流程,执行上一个状态离开的逻辑
    65. if (null != m_CurrState)
    66. m_CurrState.OnLeave();
    67. CurrStateType = newState;
    68. m_CurrState = m_dicState[CurrStateType];
    69. //然后再让新状态执行OnEnter流程,执行新状态的进入逻辑
    70. m_CurrState.OnEnter();
    71. }
    72. //设置参数值
    73. public void SetData<TData>(string key, TData value)
    74. {
    75. VariableBase itemBase = null;
    76. if (m_dicParam.TryGetValue(key, out itemBase))
    77. {
    78. //参数原来存在,更新值
    79. Variable item = itemBase as Variable;
    80. item.Value = value;
    81. //其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用
    82. //m_dicParam[key] = item;
    83. }
    84. else
    85. {
    86. //参数原来不存在,插入
    87. Variable item = new Variable();
    88. item.Value = value;
    89. m_dicParam[key] = item;
    90. }
    91. }
    92. //获取参数值
    93. public TData GetData<TData>(string key)
    94. {
    95. VariableBase itemBase = null;
    96. if (m_dicParam.TryGetValue(key, out itemBase))
    97. {
    98. Variable item = itemBase as Variable;
    99. if (null == item)
    100. return default(TData);
    101. return item.Value;
    102. }
    103. //返回该结构的实例?
    104. return default(TData);
    105. }
    106. public override void ShutDown()
    107. {
    108. if (null != m_CurrState)
    109. m_CurrState.OnLeave();
    110. //循环遍历,执行所有状态的OnDestroy函数,执行销毁逻辑,销毁所有的状态
    111. foreach (KeyValuePair<sbyte, FsmState> kvPair in m_dicState)
    112. {
    113. if (null != kvPair.Value)
    114. {
    115. kvPair.Value.OnDestroy();
    116. }
    117. }
    118. m_dicState.Clear();
    119. m_dicParam.Clear();
    120. }
    121. }
    122. }

    3.状态机 状态抽象类 FsmState.cs

    状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类FsmState ,具体的例子可以看测试代码中的实现。abstract改成接口也行,但是后来感觉没啥必要,有些派生类未必一定需要实现所有虚函数,所以就写成了抽象类,4个事件都写成了虚函数。派生类实现不实现都可。

    FsmState.cs 代码实现

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace Myh
    7. {
    8. //状态机的状态
    9. public abstract class FsmState<T> where T : class
    10. {
    11. //状态对应的状态机
    12. public Fsm CurrFsm;
    13. //进入状态
    14. public virtual void OnEnter() { }
    15. //更新状态
    16. public virtual void OnUpdate() { }
    17. //离开状态
    18. public virtual void OnLeave() { }
    19. //状态机销毁时调用(销毁状态)
    20. public virtual void OnDestroy() { }
    21. }
    22. }

    4.状态机管理器 FsmManager.cs

    状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。

    FsmManager.cs代码实现

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace Myh
    7. {
    8. //状态机管理器
    9. public class FsmManager : ManagerBase, IDisposable
    10. {
    11. //状态机字典
    12. private Dictionary<int, FsmBase> m_dicFsm;
    13. //状态机的临时编号
    14. private int m_TemFsmId = 0;
    15. public FsmManager()
    16. {
    17. m_dicFsm = new Dictionary<int, FsmBase>();
    18. }
    19. //初始化
    20. public override void Init()
    21. {
    22. }
    23. #region Create
    24. /*
    25. * 创建状态机实例
    26. * T:拥有者类型
    27. * fsmId:状态机Id
    28. * owner:拥有者实例
    29. * states:状态数组
    30. */
    31. public Fsm<T> Create<T>(int fsmId, T owner, FsmState[] states) where T : class
    32. {
    33. Fsm fsm = new Fsm(fsmId, owner, states);
    34. m_dicFsm[fsmId] = fsm;
    35. return fsm;
    36. }
    37. public Fsm<T> Create<T>(T owner, FsmState[] states) where T : class
    38. {
    39. return Create(++m_TemFsmId, owner, states);
    40. }
    41. #endregion
    42. #region 销毁状态机
    43. //销毁状态机
    44. public void DestroyFsm(int fsmId)
    45. {
    46. FsmBase fsm = null;
    47. if (m_dicFsm.TryGetValue(fsmId, out fsm))
    48. {
    49. fsm.ShutDown();
    50. m_dicFsm.Remove(fsmId);
    51. }
    52. }
    53. #endregion
    54. public void Dispose()
    55. {
    56. IEnumeratorint, FsmBase>> iter= m_dicFsm.GetEnumerator();
    57. for (; iter.MoveNext();)
    58. {
    59. iter.Current.Value.ShutDown();
    60. }
    61. m_dicFsm.Clear();
    62. }
    63. }
    64. }

    三、测试与总结

    因为刚开始写,还没办法在实际的工程中去做测试,所以主要通过按下按键就切换状态,测了一下状态切换是否符合预期,结果很好,符合预期。

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. using Myh;
    7. using YouYou;
    8. using UnityEngine;
    9. public class FmsTest : ITest
    10. {
    11. static private void PrintStateLog(int id, string strEvent)
    12. {
    13. Debug.Log("状态机:" + id + " 触发了:" + strEvent + " 事件");
    14. }
    15. #region 测试状态类
    16. enum EnumTestClassType
    17. {
    18. eTest1,
    19. eTest2,
    20. eTest3,
    21. eTest4,
    22. }
    23. class FsmTestState1 : FsmState<FmsTest>
    24. {
    25. public override void OnEnter()
    26. {
    27. base.OnEnter();
    28. PrintStateLog(1, "OnEnter");
    29. }
    30. public override void OnUpdate()
    31. {
    32. base.OnUpdate();
    33. PrintStateLog(1, "OnUpdate");
    34. }
    35. public override void OnLeave()
    36. {
    37. base.OnLeave();
    38. PrintStateLog(1, "OnLeave");
    39. }
    40. public override void OnDestroy()
    41. {
    42. base.OnDestroy();
    43. PrintStateLog(1, "OnDestroy");
    44. }
    45. }
    46. class FsmTestState2 : FsmState<FmsTest>
    47. {
    48. public override void OnEnter()
    49. {
    50. base.OnEnter();
    51. PrintStateLog(2, "OnEnter");
    52. }
    53. public override void OnUpdate()
    54. {
    55. base.OnUpdate();
    56. PrintStateLog(2, "OnUpdate");
    57. }
    58. public override void OnLeave()
    59. {
    60. base.OnLeave();
    61. PrintStateLog(2, "OnLeave");
    62. }
    63. public override void OnDestroy()
    64. {
    65. base.OnDestroy();
    66. PrintStateLog(2, "OnDestroy");
    67. }
    68. }
    69. class FsmTestState3 : FsmState<FmsTest>
    70. {
    71. public override void OnEnter()
    72. {
    73. base.OnEnter();
    74. PrintStateLog(3, "OnEnter");
    75. }
    76. public override void OnUpdate()
    77. {
    78. base.OnUpdate();
    79. PrintStateLog(3, "OnUpdate");
    80. }
    81. public override void OnLeave()
    82. {
    83. base.OnLeave();
    84. PrintStateLog(3, "OnLeave");
    85. }
    86. public override void OnDestroy()
    87. {
    88. base.OnDestroy();
    89. PrintStateLog(3, "OnDestroy");
    90. }
    91. }
    92. class FsmTestState4 : FsmState<FmsTest>
    93. {
    94. public override void OnEnter()
    95. {
    96. base.OnEnter();
    97. PrintStateLog(4, "OnEnter");
    98. }
    99. public override void OnUpdate()
    100. {
    101. base.OnUpdate();
    102. PrintStateLog(4, "OnUpdate");
    103. }
    104. public override void OnLeave()
    105. {
    106. base.OnLeave();
    107. PrintStateLog(4, "OnLeave");
    108. }
    109. public override void OnDestroy()
    110. {
    111. base.OnDestroy();
    112. PrintStateLog(4, "OnDestroy");
    113. }
    114. }
    115. #endregion
    116. private Fsm m_fsm=null;
    117. public void OnTestStart()
    118. {
    119. FsmState[] arrStates = new FsmState[4];
    120. arrStates[(int)EnumTestClassType.eTest1] = new FsmTestState1();
    121. arrStates[(int)EnumTestClassType.eTest2] = new FsmTestState2();
    122. arrStates[(int)EnumTestClassType.eTest3] = new FsmTestState3();
    123. arrStates[(int)EnumTestClassType.eTest4] = new FsmTestState4();
    124. m_fsm = GameEntry.FSM.Create(this, arrStates);
    125. }
    126. public void OnTestUpdate()
    127. {
    128. if (null == m_fsm)
    129. return;
    130. //m_fsm.OnUpdate();
    131. if (Input.GetKeyDown(KeyCode.Keypad1))
    132. {
    133. m_fsm.ChangeState((int)EnumTestClassType.eTest1);
    134. }
    135. else if (Input.GetKeyDown(KeyCode.Keypad2))
    136. {
    137. m_fsm.ChangeState((int)EnumTestClassType.eTest2);
    138. }
    139. else if (Input.GetKeyDown(KeyCode.Keypad3))
    140. {
    141. m_fsm.ChangeState((int)EnumTestClassType.eTest3);
    142. }
    143. else if (Input.GetKeyDown(KeyCode.Keypad4))
    144. {
    145. m_fsm.ChangeState((int)EnumTestClassType.eTest4);
    146. }
    147. }
    148. }

  • 相关阅读:
    卧槽,Log4j2 再爆雷,Log4j v2.17.0 横空出世。。。
    SpringCloud 05 Eureka集群环境配置和CAP
    scannet v2 数据集下载
    Redis缓存初探
    <SQL编程工具MySQL、SQLyog安装及环境配置教程>——《SQL》
    LeetCode50天刷题计划第二季(Day 31 — 两数之和 II - 输入有序数组(11.10-11.20)分数到小数(11.30-12.30)
    计算机毕业设计 基于thinphp 的校园论坛交流网站(源码+系统+mysql数据库+Lw文档)
    Ubuntu18.04 CoppeliaSim Edu 安装教程 (2022年11月)
    spring-data-mongodb的Aggregation详解
    【MAPBOX基础功能】11、mapbox绘制symbol icon图层并进行添加、删除、更新、显隐等操作
  • 原文地址:https://blog.csdn.net/qq_33531923/article/details/127742063