Unity--FSM有限状态机

有限状态机,先理解是干什么的,有限表示这个是有限度的不是无限的,状态,指的是所拥有的全部状态,这么来理解,人有情绪,好比说生气,无感,喜悦,难过,生气,幸福等,那么这些情绪是固有的几种,是所谓有限,那么那些情绪就是不一样的状态,人能够在这些状态之中进行转换,此时是开心的,下一秒有可能就是生气的,这就是有限状态机的原理。

这一篇代码稍微多了一点,我们一点一点来讲:ide


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum Transition
{
    NullTransition = 0, 
    NoSeePlayer,
    SeePlayer,
    GamePause
}

public enum StateID
{
    NullStateID = 0, 
    IdleState,
    PatrolState,
    ChaseState
}

public abstract class FSMState
{
    protected FSMSystem fsm;

    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    protected StateID stateID;
    public StateID ID { get { return stateID; } }
    
    public FSMState(FSMSystem fsm)
    {
        this.fsm = fsm;
    }

    public void AddTransition(Transition trans, StateID id)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
            return;
        }

        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
            return;
        }
        if (map.ContainsKey(trans))
        {
            Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
                           "Impossible to assign to another state");
            return;
        }

        map.Add(trans, id);
    }
    
    public void DeleteTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed");
            return;
        }
        
        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }
        Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
                       " was not on the state's transition list");
    }
    
    public StateID GetOutputState(Transition trans)
    {
        if (map.ContainsKey(trans))
        {
            return map[trans];
        }
        return StateID.NullStateID;
    }
    
    public virtual void DoBeforeEntering() { }
    
    public virtual void DoBeforeLeaving() { }

    /// <summary>
    /// 这个方法决定在当前状态的执行
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Act(GameObject player, GameObject npc);
    /// <summary>
    /// 这个方法决定状态是否应该转换到列表中的另外一个状态
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Reason(GameObject player, GameObject npc);

}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FSMSystem
{
    private List<FSMState> states;
    
    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }
    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }
    
    public FSMSystem()
    {
        states = new List<FSMState>();
    }
    
    public void Update(GameObject player,GameObject npc)
    {
        currentState.Act(player, npc);
        currentState.Reason(player, npc);
    }

    public void AddState(FSMState s)
    {
        if (s == null)
        {
            Debug.LogError("FSM ERROR: Null reference is not allowed");
        }
        
        if (states.Count == 0)
        {
            states.Add(s);
            currentState = s;
            currentStateID = s.ID;
            return;
        }
        foreach (FSMState state in states)
        {
            if (state.ID == s.ID)
            {
                Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
                               " because state has already been added");
                return;
            }
        }
        states.Add(s);
    }

    public void DeleteState(StateID id)
    {
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
            return;
        }
        foreach (FSMState state in states)
        {
            if (state.ID == id)
            {
                states.Remove(state);
                return;
            }
        }
        Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
                       ". It was not on the list of states");
    }
    
    
    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
            return;
        }

        StateID id = currentState.GetOutputState(trans);
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
                           " for transition " + trans.ToString());
            return;
        }
        
        currentStateID = id;
        foreach (FSMState state in states)
        {
            if (state.ID == currentStateID)
            {
                currentState.DoBeforeLeaving();

                currentState = state;
                
                currentState.DoBeforeEntering();
                break;
            }
        }

    } 

}

有限状态机最核心的两个脚本是FSMState和FSMSystem,其中FSMState是状态基类,FSMSystem是状态机的管理类,这两个脚本能够在Wiki.unity3d.com里搜索State找到,一个17k的文件。

在FSMState类中,有两个枚举,第一个枚举Transition存放全部状态转换的条件,第二我的枚举StateID存放的是全部的状态,当咱们增长和删除状态的时候直接在这两个枚举中添加和删除就能够了。FSMState中有一个键值对map,这个键值对存放的是状态的转换条件和目标状态,是用于在后面咱们转换状态的时候进行使用,判断是否存在这样的转换条件和状态。FSMState类中有七个函数,第一个AddTransition是进行状态转换的添加,第二个DeleteTransition是进行状态转换的删除,第三个GetOutputState是获取转换条件相应的状态,第四个DoBeforeEntering是当前状态开始时的操做,第五个DoBeforeLeaving是当前状态离开时的操做,第六个Act是当前状态执行时的操做,最后一个Reason就是转换状态的操做了。在这些的基础上我添加了一个构造函数,这个构造函数有两个做用,一是方便咱们后续对状态的标记,二是能快速的获取到FSMSystem的对象,方便操做。函数

FSMSystem类中的函数有三个,第一个AddState是注册咱们的状态,第二个DeleteState是删除状态,第三个PerformTransition就是经过转换条件进行状态之间的切换了,一样,在这个的基础上,我添加了一个函数Update,这个函数的做用是把状态的Act函数和Reason函数进行执行,由于每个状态都要一直执行这两个函数,因此直接封装起来,方便使用。this

以上是FSMState和FSMSystem两个核心类的一个说明,下面说一下有限状态机的具体使用:3d


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChaseState : FSMState {
    public ChaseState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.ChaseState;
    }

    public override void Act(GameObject player, GameObject npc)
    {

        npc.transform.LookAt(player.transform);
        npc.transform.Translate(Vector3.forward * 2 * Time.deltaTime);
    }

    public override void Reason(GameObject player, GameObject npc)
    {
       
        if (Vector3.Distance(player.transform.position, npc.transform.position)>= 3f)
        {
            Debug.Log(Vector3.Distance(player.transform.position, npc.transform.position));
            fsm.PerformTransition(Transition.GamePause);
        }
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class IdleState : FSMState
{

    public IdleState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.IdleState;
    }

    public override void Act(GameObject player, GameObject npc)
    {
    
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        if (Time.time>2)
        {
            fsm.PerformTransition(Transition.NoSeePlayer);
        }
    }

    public override void DoBeforeLeaving()
    {

       
    }

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolState : FSMState {

    GameObject[] path;
    int index = 0;

    public PatrolState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.PatrolState;
        path = GameObject.FindGameObjectsWithTag("Path");
       
    }
    public override void Act(GameObject player, GameObject npc)
    {

       
        if (Vector3.Distance(npc.transform.position,path[index].transform.position) < 1)
        {
            index++;
        }
        if (index > path.Length - 1)
        {
            index = 0;
        }
        npc.transform.LookAt(path[index].transform);
        npc.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
    }

    public override void Reason(GameObject player, GameObject npc)
    {
        if (Vector3.Distance(player.transform.position,npc.transform.position)<3f)
        {
            fsm.PerformTransition(Transition.SeePlayer);
        }
    }
    
}

首先先说ChaseState ,IdleState,PatrolState这三个类,这三个类就是具体的状态类了,它们继承了FSMState,在各自的构造函数中标记了各自表示的状态,并在FSMState的StateID枚举中进行了注册,而后根据本身不一样的操做,实现出Act函数和Reason函数。code


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {

    FSMState state; //状态
    FSMSystem fsm;  //有限状态机的管理类

    private void Start()
    {
        fsm = new FSMSystem();

        state = new IdleState(fsm);
        state.AddTransition(Transition.NoSeePlayer, StateID.PatrolState);
        fsm.AddState(state); //添加状态

        state = new PatrolState(fsm);
        state.AddTransition(Transition.SeePlayer, StateID.ChaseState);
        fsm.AddState(state);

        state = new ChaseState(fsm);
        state.AddTransition(Transition.GamePause, StateID.IdleState);
        fsm.AddState(state);
    }
    private void Update()
    {
        print("当前的状态是:" + fsm.CurrentState);
        fsm.Update(GameController.player, this.gameObject);
    }



}

状态写好之后,咱们就能够开始使用了,建立一个Enemy类进行使用,在Enemy类中,咱们要获取到状态基类FSMState和FSMSystem类的对象,以备使用,在Start函数中赋值,同时取得每一种状态的对象经过AddTransition函数把当前状态能够转换到的状态和转换条件注册到FSMState的键值对map中,再经过FSMSystem的AddState函数注册了当前状态,这样状态就所有准备就绪了。orm

这里有个点说一下,为何在键值对中注册还要在AddState函数中注册状态呢?由于当转换状态时,首先会调用FSMSystem中的Update函数以后会执行Reason函数,在Reason函数中,咱们会调用FSMSystem中的PerformTransition函数进行转换操做,在PerformTransition中,首先要判断当前的转换条件是否不为null,而后经过GetOutputState函数获得转换条件对应的状态,这里就须要用到FSMState中的键值对进行判断了,若是不注册转换条件和目标状态,在这一步就不会获得相应的状态,拿到目标状态以后,要判断是否存在这个状态,就会遍历FSMSystem中的状态列表,存在的话就开始具体的状态切换了要把当前状态变换为目标状态,在此以前要执行当前状态的DoBeforeLeaving函数,切换了状态以后执行DeBeforeEntering函数,这样就完成了总体的一个状态切换了。对象

最后只须要在Enemy类的Update函数中执行FSMSystem类的Update函数就行了。继承


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour {
    
    public static GameObject player;
  
    private GameController() { }
    private static GameController gameController;
    public static GameController GetGameController { get { return gameController; } }

    private void Awake()
    {
        gameController = this;
        player = GameObject.Find("Player");
    }
    
}

到此,FSM有限状态机的一个操做和理解也就结束了,Over!