Unity 游戏框架搭建 (四) 简易有限状态机

为什么用有限状态机?

之前做过一款跑酷游戏,跑酷角色有很多状态:跑、跳、二段跳、死亡等等。一开始是使用 if/switch 来切换状态,但是每次角色添加一个状态(提前没规划好),所有状态处理相关的代码就会指数级增长,那样就会嗅出代码的坏味道了。在这种处理状态并且状态数量不是特别多的情况下,自然就想到了引入状态机。

优点:

  • 使代码整洁,状态容易扩展和管理。
  • 可复用。
  • 还没想到…..缺点:
  • 也没想到……

什么是有限状态机?

解释不清楚,看了下百度百科。反正是一种数据结构,一个解决问题的工具。从百度百科可以看到,有限状态机最最最基础的概念有两个:状态和转移。从刚才跑酷的例子来讲,跑、跳、二段跳等这些就是角色的状态。

如图所示:4.简易有限状态机  - 图1主角从跑状态切换到跳状态,从跳状态切换到二段跳状态,这里的切换就是指状态的转移。状态的转移是有条件的,比如主角从跑状态不可以直接切换到二段跳状态。但是可以从二段跳状态切换到跑状态。

另外,一个基本的状态有:进入状态、退出状态、接收输入、转移状态等动作。但是仅仅作为跑酷的角色的状态管理来说,只需要转移状态就足够了。有兴趣的同学可以自行扩展。

如何实现?

恰好之前看到过一个还算简易的实现(简易就是指我能看得懂- -,希望大家也是),原版是用lua实现的,我的跑酷游戏是用C# 实现的,所以直接贴出C#代码。

  1. namespace QFramework
  2. {
  3. using System.Collections.Generic;
  4.  
  5. /// <summary>
  6. /// 教程地址:http://liangxiegame.com/post/4/
  7. /// </summary>
  8. public class QFSMLite
  9. {
  10. /// <summary>
  11. /// FSM callfunc.
  12. /// </summary>
  13. public delegate void FSMCallfunc(params object[] param);
  14.  
  15. /// <summary>
  16. /// QFSM state.
  17. /// </summary>
  18. class QFSMState
  19. {
  20. private string mName;
  21.  
  22. public QFSMState(string name)
  23. {
  24. mName = name;
  25. }
  26.  
  27. /// <summary>
  28. /// The translation dict.
  29. /// </summary>
  30. public readonly Dictionary<string, QFSMTranslation> TranslationDict = new Dictionary<string, QFSMTranslation>();
  31. }
  32.  
  33. /// <summary>
  34. /// Translation
  35. /// </summary>
  36. public class QFSMTranslation
  37. {
  38. public string FromState;
  39. public string Name;
  40. public string ToState;
  41. public FSMCallfunc OnTranslationCallback; // 回调函数
  42.  
  43. public QFSMTranslation(string fromState, string name, string toState, FSMCallfunc onTranslationCallback)
  44. {
  45. FromState = fromState;
  46. ToState = toState;
  47. Name = name;
  48. OnTranslationCallback = onTranslationCallback;
  49. }
  50. }
  51.  
  52. public string State { get; private set; }
  53.  
  54. /// <summary>
  55. /// The m state dict.
  56. /// </summary>
  57. private readonly Dictionary<string, QFSMState> mStateDict = new Dictionary<string, QFSMState>();
  58.  
  59. /// <summary>
  60. /// Adds the state.
  61. /// </summary>
  62. /// <param name="name">Name.</param>
  63. public void AddState(string name)
  64. {
  65. mStateDict[name] = new QFSMState(name);
  66. }
  67.  
  68. /// <summary>
  69. /// Adds the translation.
  70. /// </summary>
  71. /// <param name="fromState">From state.</param>
  72. /// <param name="name">Name.</param>
  73. /// <param name="toState">To state.</param>
  74. /// <param name="callfunc">Callfunc.</param>
  75. public void AddTranslation(string fromState, string name, string toState, FSMCallfunc callfunc)
  76. {
  77. mStateDict[fromState].TranslationDict[name] = new QFSMTranslation(fromState, name, toState, callfunc);
  78. }
  79.  
  80. /// <summary>
  81. /// Start the specified name.
  82. /// </summary>
  83. /// <param name="name">Name.</param>
  84. public void Start(string name)
  85. {
  86. State = name;
  87. }
  88.  
  89. /// <summary>
  90. /// Handles the event.
  91. /// </summary>
  92. /// <param name="name">Name.</param>
  93. /// <param name="param">Parameter.</param>
  94. public void HandleEvent(string name, params object[] param)
  95. {
  96. if (State != null && mStateDict[State].TranslationDict.ContainsKey(name))
  97. {
  98. QFSMTranslation tempTranslation = mStateDict[State].TranslationDict[name];
  99. tempTranslation.OnTranslationCallback(param);
  100. State = tempTranslation.ToState;
  101. }
  102. }
  103.  
  104. /// <summary>
  105. /// Clear this instance.
  106. /// </summary>
  107. public void Clear()
  108. {
  109. mStateDict.Clear();
  110. }
  111. }
  112. }

测试代码(需自行修改):

  1. mPlayerFsm = new QFSMLite();
  2.  
  3. // 添加状态
  4. mPlayerFsm.AddState(STATE_DIE);
  5. mPlayerFsm.AddState(STATE_RUN);
  6. mPlayerFsm.AddState(STATE_JUMP);
  7. mPlayerFsm.AddState(STATE_DOUBLE_JUMP);
  8. mPlayerFsm.AddState(STATE_DIE);
  9.  
  10. // 添加跳转
  11. mPlayerFsm.AddTranslation(STATE_RUN, EVENT_TOUCH_DOWN, STATE_JUMP, JumpThePlayer);
  12. mPlayerFsm.AddTranslation(STATE_JUMP, EVENT_TOUCH_DOWN, STATE_DOUBLE_JUMP, DoubleJumpThePlayer);
  13. mPlayerFsm.AddTranslation(STATE_JUMP, EVENT_LAND, STATE_RUN, RunThePlayer);
  14. mPlayerFsm.AddTranslation(STATE_DOUBLE_JUMP, EVENT_LAND, STATE_RUN, RunThePlayer);
  15.  
  16. // 启动状态机
  17. mPlayerFsm.Start(STATE_RUN);

就这些,想要进一步扩展的话,可以给FSMState类添加EnterCallback和ExitCallback等委托,然后在FSM的HandleEvent方法中进行调用。当时对跑酷的项目来说够用了,接没继续扩展了,我好懒- -,懒的借口是:没有最好的设计,只有最适合的设计,233333。

相关链接:

我的框架地址:https://github.com/liangxiegame/QFramework

教程源码:https://github.com/liangxiegame/QFramework/tree/master/Assets/HowToWriteUnityGameFramework/

QFramework &游戏框架搭建QQ交流群: 623597263

转载请注明地址:凉鞋的笔记http://liangxiegame.com/

微信公众号:liangxiegame

4.简易有限状态机  - 图2

如果有帮助到您:

如果觉得本篇教程对您有帮助,不妨通过以下方式赞助笔者一下,鼓励笔者继续写出更多高质量的教程,也让更多的力量加入 QFramework 。