美文网首页
[个人框架 C_Framework] FSM状态机

[个人框架 C_Framework] FSM状态机

作者: hh5460 | 来源:发表于2018-01-18 18:16 被阅读0次

该状态机逻辑

该状态机逻辑.png 按下启动O状态.png 按下启动M状态,A Stay开始执行.png 按下启动N状态,A Stay持续执行.png

测试代码

using UnityEngine;

public class C_FSM_Test: MonoBehaviour
{
    C_FSMCtrl fsmC;
    C_FSMState stateA;     //也可以通过 fsmC.dicALLStates["StateA"] 来获取,此处演示,直接拿到stateA实例
    // Use this for initialization
    void Start()
    {
        fsmC = new C_FSMCtrl(this.name);
        stateA = fsmC.AddState("StateA", () => { Debug.Log("A Start!!!"); }, () => { Debug.Log("A Over!!!"); }, () => { Debug.Log("A Stay Continue Print"); });

        fsmC.AddState("StateB", () => { Debug.Log("B Start!!!"); }, () => { Debug.Log("B Over!!!"); });
        fsmC.AddState("StateC", () => { Debug.Log("C Start!!!"); }, () => { Debug.Log("C Over!!!"); });

        //通过指定父状态C_FSMState对象的方式添加当前状态到stateA状态
        fsmC.AddState("StateD", () => { Debug.Log("E Start!!!"); }, () => { Debug.Log("E Over!!!"); },null, stateA);
        fsmC.AddState("StateE", () => { Debug.Log("F Start!!!"); }, () => { Debug.Log("F Over!!!"); },null, stateA);
        fsmC.AddState("StateF", () => { Debug.Log("G Start!!!"); }, () => { Debug.Log("G Over!!!"); },null, stateA);

        //通过指定父状态字符串的方式添加状态
        fsmC.AddState("StateG", "StateB", () => { Debug.Log("G Start!!!"); }, () => { Debug.Log("G Over!!!"); });
        fsmC.AddState("StateH", "StateC", () => { Debug.Log("H Start!!!"); }, () => { Debug.Log("H Over!!!"); });
        fsmC.AddState("StateI", "StateC", () => { Debug.Log("I Start!!!"); }, () => { Debug.Log("I Over!!!"); });
        fsmC.AddState("StateJ", "StateD", () => { Debug.Log("J Start!!!"); }, () => { Debug.Log("J Over!!!"); });
        fsmC.AddState("StateK", "StateD", () => { Debug.Log("K Start!!!"); }, () => { Debug.Log("K Over!!!"); });
        fsmC.AddState("StateO", "StateH", () => { Debug.Log("O Start!!!"); }, () => { Debug.Log("O Over!!!"); });
        fsmC.AddState("StateL", "StateJ", () => { Debug.Log("L Start!!!"); }, () => { Debug.Log("L Over!!!"); });
        fsmC.AddState("StateM", "StateK", () => { Debug.Log("M Start!!!"); }, () => { Debug.Log("M Over!!!"); });
        fsmC.AddState("StateN", "StateK", () => { Debug.Log("N Start!!!"); }, () => { Debug.Log("N Over!!!"); });
    }

    // Update is called once per frame
    void Update()
    {
        //为什么在此处调用,如果在FSMState自身调用,那么所有的状态都会调用,即使它没有任何实质函数内容
        //但放在此处可以按需调用,也可以放在FixedUpdate中调用,或者定时调用,更加灵活
        stateA.stay();

        if (Input.GetKeyDown(KeyCode.O))
        {
            Debug.Log("按下启动O状态");
            fsmC.StartState("StateO");
        }

        if (Input.GetKeyDown(KeyCode.M))
        {
            Debug.Log("按下启动M状态");
            fsmC.StartState("StateM");
        }

        if (Input.GetKeyDown(KeyCode.N))
        {
            Debug.Log("按下启动N状态");
            fsmC.StartState("StateN");
        }     
    }
}
using System;
using System.Collections.Generic;

/// <summary>
/// FSM状态
/// </summary>
public class C_FSMState
{
    //当前状态是否处于激活状态
    public bool isActivity;

    //当前状态的路径,路径记录了父类状态名称 例如 ROOT/StateA/StateB/StateNow
    public string path;

    //记录当前状态开始时的委托
    public Action act_start;

    //记录当前状态结束时的委托
    public Action act_over;

    //记录当状态stay驻留时候的委托,这个方法可以放在绑定物体的update()函数中重复执行
    public Action act_stay;

    //记录当前状态所属于的状态控制器
    public C_FSMCtrl fsmCtrl;           //控制器

    //当前状态名称
    public string StateName;

    //当前状态的所属的父状态
    public C_FSMState StateFather;

    //当前状态的子状态容器
    public Dictionary<string, C_FSMState> dicChilds;              //子节点

    /// <summary>
    /// 仅包含一个名称的状态的构造函数
    /// </summary>
    /// <param name="stateName">状态名</param>
    public C_FSMState(string stateName)
    {
        StateName = stateName;
        path = StateName;
    }

    /// <summary>
    /// 一个状态的构造函数
    /// </summary>
    /// <param name="stateName">状态名</param>
    /// <param name="_act_start">当前状态开始时的委托</param>
    /// <param name="_act_over">当前状态关闭时的委托</param>
    public C_FSMState(string stateName, Action _act_start = null, Action _act_over = null, Action _act_stay = null)
    {
        StateName = stateName;
        act_start = _act_start;
        act_over = _act_over;
        act_stay = _act_stay;
        path = StateName;
    }

    /// <summary>
    /// 开始一个状态
    /// </summary>
    public void start()
    {
        //若当前状态已经激活
        if (isActivity == true)
        {
            return;
        }
        //如果当前状态的父状态不为空,切父状态未开启,则先开启父状态
        if (this.StateFather != null && this.StateFather.isActivity == false)
        {
            this.StateFather.start();
        }
        
        //将当前状态至于开启状态
        isActivity = true;
        
        //设置当前状态控制器为当前状态为此状态
        fsmCtrl.currentState = this;

        //如果设置了开启状态的回调函数,则进行回调
        if (act_start != null)
        {
            act_start();
        }
    }

    /// <summary>
    /// 记录当状态stay驻留时候的委托,这个方法可以放在绑定物体的update()函数中重复执行
    /// </summary>
    public void stay()
    {
        if (isActivity && act_stay!=null)
        {
            act_stay();
        }
    }

    /// <summary>
    /// 状态结束
    /// </summary>
    public void over()
    {
        //如果当前状态已经处于结束状态则返回
        if (isActivity == false)
        {
            return;
        }

        //如果当前状态存在子状态
        if (this.dicChilds != null)
        {
            //则逐级关闭当前状态
            var emut = dicChilds.GetEnumerator();
            while (emut.MoveNext())
            {
                emut.Current.Value.over();
            }
        }
        //将当前状态激活状态关闭
        isActivity = false;

        //如果状态控制器的 当前状态为自己,则关闭自己后将其设置为父状态
        if (fsmCtrl.currentState != null && fsmCtrl.currentState == this)
        {
            fsmCtrl.currentState = StateFather;
        }
        //如果当前状态的关闭委托不为空,则调用当前委托
        if (act_over != null)
        {
            act_over();
        }
    }
}
using System;
using System.Collections.Generic;

public class C_FSMCtrl
{
    private string rootStateName = "root";                  //根状态名
    public C_FSMState rootState;                            //根状态
    public C_FSMState currentState;                         //当前状态
    public string ctrlName;                                 //状态控制器名称
    public Dictionary<string, C_FSMState> dicALLStates;     //该控制器下的所有状态机

    /// <summary>
    /// 构造一个状态机控制器
    /// </summary>
    /// <param name="name">当前状态机控制器名称</param>
    public C_FSMCtrl(string name)
    {
        //当前状态机控制器名称
        ctrlName = name;
        //实例化一个状态字典,用于存储所有的子状态
        dicALLStates = new Dictionary<string, C_FSMState>();
        //实例化一个根状态
        rootState = new C_FSMState(rootStateName);
        //如果所有的状态中不包含当前的根状态,则将其加入当所有状态中
        if (!dicALLStates.ContainsKey(name))
        {
            //设置根状态的控制器为自身
            rootState.fsmCtrl = this;
            //将根状态加入到当前控制器状态集合中
            dicALLStates.Add(name, rootState);
        }
        //将当前状态控制器加入到 所有状态控制 背包,该背包存储所有的状态控制器
        C_FSMCtrlBags.AddFsmc(this);
    }

    /// <summary>
    /// 为当前的状态控制器增加一个状态
    /// </summary>
    /// <param name="stateName">状态名称</param>
    /// <param name="_act_start">状态开始时候的回调函数</param>
    /// <param name="_act_over">状态结束时的回掉函数</param>
    /// <param name="C_FSMState">返回增加的状态</param>
    public C_FSMState AddState(string stateName,Action _act_start = null, Action _act_over = null, Action _act_stay = null,C_FSMState parentState = null)
    {
        //新建一个子状态
        C_FSMState childState = null;
        //如果当前状态控制器已经包含该子状态则自己取出该状态
        if (dicALLStates.ContainsKey(stateName))
        {
            childState = dicALLStates[stateName];
        }
        else
        {
            //如果当前控制器不包含该子状态,则新建该状态
            childState = new C_FSMState(stateName);
        }

        //更新它绑定的三个委托
        childState.act_start = _act_start;
        childState.act_over = _act_over;
        childState.act_stay = _act_stay;

        //若没有设置父节点,则默认为rootState节点
        if (parentState == null)
        {
            parentState = rootState;
        }

        //如果当前状态的父状态存储子类状态的字典不存在
        if (parentState.dicChilds == null)
        {
            //则实例化子类状态存储的容器
            parentState.dicChilds = new Dictionary<string, C_FSMState>();
        }

        //如果当前状态的父状态不等于null则表明当前已经存在父状态,那么就相当于是要切换父状态,需将原先的父状态删除
        if (childState.StateFather != null) // && childState.StateFather.dicChilds != null && childState.StateFather.dicChilds.ContainsKey(stateName))
        {
            childState.StateFather.dicChilds.Remove(stateName);
        }

        //指定当前状态的父状态
        childState.StateFather = parentState;
        //将当前状态的加入到父状态的子状态字典中
        parentState.dicChilds.Add(childState.StateName, childState);
        //设置子状态的路径
        childState.path = parentState.path + "/" + childState.StateName;

        //如果当前状态控制器不包含新状态的名称,则表示是新建状态,需要添加到字典中
        //否则则表示新状态已经在控制器中了,说明是修改状态,修改后无需重复添加
        if (!dicALLStates.ContainsKey(stateName))
        {
            //指定当前控制器为自身
            childState.fsmCtrl = this;
            //将新生成的状态追加到状态控制器字典中
            dicALLStates.Add(stateName, childState);
        }
        return childState;
    }

    /// <summary>
    /// 为当前的状态控制器增加一个状态
    /// </summary>
    /// <param name="stateName">状态名称</param>
    /// <param name="parentStateName">指定父状态名称</param>
    /// <param name="_act_start">状态开始时候的回调函数</param>
    /// <param name="_act_over">状态结束时的回掉函数</param>
    /// <param name="C_FSMState">返回增加的状态</param>
    public C_FSMState AddState(string stateName, string parentStateName, Action _act_start = null, Action _act_over = null,Action _act_stay=null)
    {
        C_FSMState parentState = rootState;
        //如果指定的当前父状态名称不等于默认的根状态名称,则表示当前的状态并不是要设置在根状态下
        if (parentStateName != rootStateName)
        {
            //从当前状态机中取出需要设置的根状态
            if (dicALLStates.ContainsKey(parentStateName))
            {
                parentState = dicALLStates[parentStateName];
            }
            else
            {
                //如果不存在则抛出异常,指定当前状态的父状态不存在
                throw new System.Exception("名为:" + ctrlName + "的状态控制器不存在" + parentStateName + "索引");
            }
        }
        return AddState(stateName, _act_start, _act_over, _act_stay, parentState);
    }

    //移除状态机,但是不销毁
    public void RemoveState(string stateName)
    {
        //如果当前状态控制器中包含指定名称的状态
        if (dicALLStates.ContainsKey(stateName))
        {
            //取出当前状态
            C_FSMState state = dicALLStates[stateName];
            //如果当前状态是激活状态,则调用over终止状态
            if (state.isActivity)
            {
                state.over();
            }
            //递归回收该状态和所有子状态
            RecursionStop(state);
        }
    }

    //开始一个状态
    public void StartState(string stateName)
    {
        //如果当前控制器中包含指定名称的状态
        if (dicALLStates.ContainsKey(stateName))
        {
            //需要开始的状态
            C_FSMState nextState = dicALLStates[stateName];
            if (currentState != null)     //如果控制器当前的状态不为空,则表示当前处于一个状态中,说明是切换状态
            {
                //需要开始的状态的父状态不为空,而且当前状态的父状态和需要开始的父状态相同,则表示同父是亲兄弟状态
                if (nextState.StateFather != null && nextState.StateFather == currentState.StateFather)
                {
                    //亲兄弟
                    //表示是兄弟状态允许切换,先终止当前状态,然后开始另外的状态
                    currentState.over();
                    nextState.start();
                }
                else //表示他们的父状态不同
                {
                    /*
                                      ROOT
                               |     |     |     |
                               A     B     C     D
                             |   |   |   |   |  | |
                             E   F   G   H   I  J K
                     
                     假设当前状态是E,需要切换到H
                     1.找到他们相同的祖先(此处例子为ROOT),当前激活状态向上递归over()直到相同祖先(不包含相同祖先),切换后的状态递归start()直到相同祖先(不包含相同祖先),即
                     2.I状态over()
                     3.C状态over()
                     4.A状态start()
                     5.E状态start()
                                                    
                     */
                    //非亲兄弟, 则需要先回溯找到共同的父亲节点,然后切换,当前状态逐级向上调用over()  
                    //1.找到公共父类节点
                    string[] arrPath1 = currentState.path.Split('/');
                    string[] arrPath2 = nextState.path.Split('/');
                    //防止索引越界,根据索引小遍历
                    int length = Math.Min(arrPath1.Length, arrPath2.Length);
                    //共同父状态名称
                    string togetherState = "";
                    //路径从根往下对比
                    //ROOT/A/E
                    //ROOT/C/I
                    //取得相同路径ROOT即为共同祖先
                    for (int i = 0; i < length; i++)
                    {
                        if (arrPath1[i] != arrPath2[i])
                        {
                            break;
                        }
                        else
                        {
                            togetherState = arrPath1[i];
                        }
                    }
                    /*由于FSMState中已经默认使用了递归开启和关闭,所以下面递归关闭就多余了,直接关闭和打开对应的节点即可
                    //将当前状态从下级往上级逐级关闭
                    for (int i = arrPath1.Length - 1; i >= 0; i--)
                    {
                        if (arrPath1[i] == togetherState)
                        {
                            break;
                        }
                        dicALLStates[arrPath1[i]].over();
                    }

                    //将需要新打开的状态从上级往下级逐级开启
                    bool isStart = false;
                    for (int i = 0; i < arrPath2.Length; i++)
                    {
                        if (togetherState == arrPath2[i])
                        {
                            isStart = true;
                            continue;
                        }

                        if (isStart)
                        {
                            dicALLStates[arrPath2[i]].start();
                        }
                    }
                    */

                    //ROOT/C/I 之需要关闭C状态即可,默认会自动递归关闭I状态
                    bool selectNext = false;
                    for (int i = 0; i < arrPath1.Length; i++)
                    {
                        if (togetherState == arrPath1[i])
                        {
                            selectNext = true;
                            continue;
                        }

                        if (selectNext)
                        {
                            dicALLStates[arrPath1[i]].over();
                            break;
                        }
                    }

                    //ROOT/A/E 直接开启E状态即可,默认会自动递归开启A状态
                    dicALLStates[arrPath2[arrPath2.Length - 1]].start();
                }
            }
            else //如果当前状态机并没有开启任何状态,则开始当前状态
            {
                nextState.start();
            }
            ////将当前状态机指向新开启的状态机,无需指定,start()函数中,默认会绑定
            //currentState = nextState;
        }
        else
        {
            //当需要开启的状态机不存在时
            throw new Exception(ctrlName + "状态机字典中不存在对应的的状态" + stateName);
        }
    }

    //停止状态
    public void StopState(string stateName)
    {
        //如果当前状态存在,则关闭,默认会递归调用子类的状态进行关闭
        if (dicALLStates.ContainsKey(stateName))
        {
            dicALLStates[stateName].over();
        }
    }

    //直接递归销毁子类状态并且移除,不调用over
    void RecursionStop(C_FSMState state)
    {
        //如果传入的状态的子状态不为空
        if (state.dicChilds != null)
        {
            //则获取所有的子状态,仅包括子,不包括孙或更下一辈
            List<C_FSMState> lstChilds = new List<C_FSMState>();
            foreach (var item in state.dicChilds)
            {
                lstChilds.Add(item.Value);
            }

            //递归调用子类的
            for (int i = 0; i < lstChilds.Count; i++)
            {
                RecursionStop(lstChilds[i]);
            }
        }

        //如果当前状态存在父状态,则将当前状态从父状态中移除
        if (state.StateFather != null)
        {
            state.StateFather.dicChilds.Remove(state.StateName);
        }

        //如果控制器中包含当前状态,则将当前状态从控制器中移除
        if (dicALLStates.ContainsKey(state.StateName))
        {
            dicALLStates.Remove(state.StateName);
        }

        //清楚当前的状态的子字典,路径,父状态,父名称
        state.dicChilds = null;
        state.path = null;
        state.StateFather = null;
        state.StateName = "";
    }
}
using System.Collections.Generic;

public class C_FSMCtrlBags{

    //存储所有状态控制器的Bags
    static Dictionary<string, C_FSMCtrl> dicFsmcs = new Dictionary<string, C_FSMCtrl>();

    //根据字符串索引状态控制器
    public C_FSMCtrl this[string indexStr]
    {
        get
        {
            //如果存在则返回,如果不存在则抛出异常
            C_FSMCtrl res = null;
            if (dicFsmcs.ContainsKey(indexStr))
            {
                res = dicFsmcs[indexStr];
            }
            else
            {
                throw new System.Exception("FSMCtrlBags不存在指定的索引"+ indexStr);
            }
            return res;
        }
    }

    //将控制器加入到控制器背包中
    public static void AddFsmc(C_FSMCtrl fsmc)
    {
        if (!dicFsmcs.ContainsKey(fsmc.ctrlName))
        {
            dicFsmcs.Add(fsmc.ctrlName, fsmc);
        }
        else
        {
            throw new System.Exception("FSMCtrlBags已存在key:" + fsmc.ctrlName);
        }
    }
}

相关文章

网友评论

      本文标题:[个人框架 C_Framework] FSM状态机

      本文链接:https://www.haomeiwen.com/subject/aabwoxtx.html