一. 什么为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 的生命周期函数(极简版):

  1. OnEnter() //当状态进入时刻
  2. OnUpdate() //当前状态持续进行中 ,状态转换条件往往在这里进行判断。
  3. 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?
  1. 声明state名称的常量若干。
  2. MonoBehaviours Start方法中 new 一个 FSM。
  3. FSM 实例中 AddState(new XXState())。
  4. MonoBehaviours Update方法中 调 FSM 实例 的Update 方法。
  5. 状态转换 调用 FSM实例的 ChangeState() 方法。

Tips: FSM在哪 new 好点?

  1. 如果FSM算的上一个Mananger的话,他需要一个Manager来持有,也就是Manager of Manager (高内聚)。
  2. 如果这个 FSM 是一个 AI 的大脑,那就让这个AI 身上的主脚本持有,减少其他不必要的访问(迪米特法则)。
写到最后:
  • 文章最后贴的代码只是 FSM 理念的一种实现,相较于常见实现,可以明显发现少了 一个 Transition理念。可类比于 Animator 或者 下面 贴的扩展阅读中的 FSM。

  • 限于笔者水平,难免有遗漏,欢迎留言斧正共同进步。

扩展阅读:
  1. http://wiki.unity3d.com/index.php/Finite_State_Machine
  2. 控制反转的简易FSM实现