Unity--FSM有限状态机

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

这一篇代码稍微多了一点,咱们一点一点来说:


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 map = new Dictionary();
    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() { }

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

} 

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

public class FSMSystem
{
    private List states;
    
    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }
    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }
    
    public FSMSystem()
    {
        states = new List();
    }
    
    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函数进行执行,因为每一个状态都要一直执行这两个函数,所以直接封装起来,方便使用。

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


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函数。


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函数注册了当前状态,这样状态就全部准备就绪了。

这里有个点说一下,为什么在键值对中注册还要在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!

你可能感兴趣的:(实战核心技术,AI,Unity,C#,FSM,有限状态机)