FSM有限状态机的实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FSM有限状态机的实现相关的知识,希望对你有一定的参考价值。

    参考原地址:http://www.manew.com/thread-48266-1-1.html

 

    在查看了这么多状态机的文章后,终于找到个看懂的了,在此很感谢那位作者。果然还是要把代码下载下来慢慢分析才知道。

 

    普通的AI逻辑都很简单,都是switch/case这样的形式,这个形式跟简单工厂模式非常类似,然而我们的工作是把模式改为工厂模式,即把那些case改为单独的状态类。

  

    先从最简单的状态类来说明,先来一个状态抽象基类:FSMState。所有的状态类都继承自这个基类,比如巡逻类、攻击类。作为所有状态类的基类,这个类就要有一些所有状态类都有的一些行为:

    /// <summary>
    /// 判断是否需要转到其他状态
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Judge(Transform player, Transform npc);

    /// <summary>
    /// 执行当前状态
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Excute(Transform player, Transform npc);

     函数意思都很简单了,如果需要说明的话就私聊吧....

 

     然后为了便于管理每个状态所以都会有个自己的编号,还有个字典用在Judge里处理状态的改变的....代码在下面,字典的key存储可能发生的事件,value存储就是刚才说的状态的编号。(比如当看见玩家的时候就去追逐,对照代码看

protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>();

//定义枚举,为可能的转换分配编号
public enum Transition
{
  SawPlayer = 0, //看到玩家
    ReachPlayer,   //接近玩家
    LostPlayer,    //玩家离开视线
    NoHealth,      //死亡
}

/// <summary>
/// 定义枚举,为可能的状态分配编号ID
/// </summary>
public enum FSMStateID
{
  Patrolling = 0,  //巡逻编号
    Chasing,         //追逐编号
    Attacking,       //攻击编号
    Dead,           //死亡编号
}

 

  比如我们在一个巡逻类里的Judge判断,当看见玩家的时候,通过查找字典Transition.SawPlayer去得到其value:FSMStateID.Chasing。下面的代码看不懂什么AIController.SetTransition是什么没关系,只需要知道里面封装的是敌人看见玩家后通过查找字典得到一个要追逐玩家的方法。

    public override void Judge(Transform player, Transform npc)
    {
        if (Vector3.Distance(npc.position, player.position) <= chaseDistance)
        {//通知控制器看见玩家了
                npc.GetComponent<AIController>().SetTransition(Transition.SawPlayer);
        }
    }

  在各个状态类里,其实都只是相当于填空题一样填上Judge(状态的判断和切换)和Excute(状态的行为),FSMState剩下的就是对字典的各种封装什么,比如添加一组Key,Value什么的....,当然也可以有删除,童鞋们自己去封装吧。

    /// <summary>
    /// 向字典添加项
    /// </summary>
    /// <param name="trans"></param>
    /// <param name="id"></param>
    public void AddTransition(Transition trans, FSMStateID id)
    {
        if (map.ContainsKey(trans))
        {
            return;
        }

        map.Add(trans, id);

    }

 

  讲完了FSMState之后,就需要有一个状态机来管理...还是先给基类FSM:

 

 public class FSM : MonoBehaviour
{
    protected virtual void Init() { }
    protected virtual void FSMUpdate() { }
    protected virtual void FSMFixedUpdate() { }

    void Start()
    {
        Init();
    }

    void Update()
    {
        FSMUpdate();
    }

    void FixedUpdate()
    {
        FSMFixedUpdate();
    }
}

  这里的还是虚方法,留给后人(派生类)来填写,后人只需要重写这些方法就相当于能够在Start、Update中执行了。然后有个叫AdvanceFSM的类继承它了,它就负责管理所有的状态和存储当前状态还有一个当前状态的编号,FSM是负责Monobehaior沟通的。里面有一个管理所有状态的链表,然后就飞天了~~方法都是关于这个链表的~~比如封装添加元素到链表、通过这个链表查找到需要转换到的新状态。

    /// <summary>
    /// 添加状态到链表中记录
    /// </summary>
    /// <param name="fsmState"></param>
    public void AddFSMState(FSMState fsmState)
    {
        if (fsmState == null)
        {
            return;   
        }

        //如果插入的这个状态时,列表还是空的,那么将它加入列表并返回
          if (fsmStates.Count == 0)
        {
            fsmStates.Add(fsmState);
            currentState = fsmState;
            currentStateID = fsmState.ID;
            return;
        }

        foreach (FSMState item in fsmStates)
        {
            if (item.ID == fsmState.ID)
            {
                return;
            }
        }

        fsmStates.Add(fsmState);
    }

    /// <summary>
    /// 根据当前状态,和传递的参数,转换新状态
    /// </summary>
    /// <param name="trans"></param>
    public void PerformTransition(Transition trans)
    {
        FSMStateID id = currentState.GetOutputState(trans);

        currentStateID = id;

        foreach (FSMState item in fsmStates)
        {//遍历状态链表,找到符合的状态
            if (item.ID == currentStateID)
            {
                currentState = item;
                break;
            }
        }
    }

  然而这个类居然还有类继承,也就是要贴给敌人的脚本了:AIController。在这个类里去初始化所有的信息,顺便填空填上刚才FSM的几个虚方法。

public class AIController : AdvanceFSM {

    /// <summary>
    /// 基类的Init在Monobehavior.Start()中
    /// </summary>
    protected override void Init()
    {
        //得到玩家的信息
          GameObject objPlayer = GameObject.FindGameObjectWithTag("Player");
        playerTransform = objPlayer.transform;

        ConstructFSM();
    }

    protected override void FSMUpdate()
    {
        timer += Time.deltaTime;
    }

    protected override void FSMFixedUpdate()
    {
        CurrentState.Judge(playerTransform, transform);
        CurrentState.Excute(playerTransform, transform);
    }

    private void ConstructFSM()
    {
        pointList = GameObject.FindGameObjectsWithTag("PatrolPoint");

        Transform[] waypoints = new Transform[pointList.Length];
        int i = 0;

        foreach (GameObject item in pointList)
        {
            waypoints[i] = item.transform;
            i++;
        }

        PatrolState patrol = new PatrolState(waypoints);
        patrol.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        patrol.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        ChaseState chase = new ChaseState();
        chase.AddTransition(Transition.ReachPlayer, FSMStateID.Attacking);
        chase.AddTransition(Transition.LostPlayer, FSMStateID.Patrolling);

        AttackState attack = new AttackState();
        attack.AddTransition(Transition.LostPlayer, FSMStateID.Patrolling);
        attack.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        attack.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        AddFSMState(patrol);
        AddFSMState(chase);
        AddFSMState(attack);
    }

    public void SetTransition(Transition t)
    {
        PerformTransition(t);
    }

}

  以上只是对这个代码的个人见解,有什么不明白或者错误欢迎告知,谢谢。

 

      要下载代码的童鞋请到最上面提到的参考地址里下载。

 

以上是关于FSM有限状态机的实现的主要内容,如果未能解决你的问题,请参考以下文章

Unity FSM 有限状态机

如何以面向对象的思想设计有限状态机

(有限)状态机

状态机的一般实现

状态机的Go语言实现版本

有限状态机FSM详解及其实现