[Unity3D] 漫谈有限状态机(FSM)

在本文 ,笔者就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 的生命周期函数(极简版):

  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实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容