天天看點

【最通俗易懂】C#有限狀态機

翻譯:http://wiki.unity3d.com/index.php/Finite_State_Machine

有限狀态機

表示有限個狀态以及在這些狀态之間的轉換和動作等行為的數學模型。

有限狀态機架構:

  • Transition enum:此枚舉包含可由系統觸發的轉換标簽。
  • StateID枚舉:這是遊戲具有的狀态ID。
  • FSMState類:有限狀态機系統中的狀态。每個狀态都有一個字典,字典中有(轉換-狀态)鍵值對,儲存Transition轉換枚舉類型的Key時,把枚舉類型轉換為int類型。(Enum沒有實作IEquatable接口。是以,當我們使用Enum類型作為key值時,Dictionary的内部操作就需要将Enum類型轉換為System.Object,這就導緻了Boxing的産生。)
  • FSMSystem:這是有限狀态機類,遊戲中的每個NPC或GameObject必須具有這些類才能使用該架構。它将NPC的狀态存儲在List中,具有添加和删除狀态的方法以及基于傳遞給它的轉換來更改目前狀态的方法PerformTransition()。您可以在代碼中的任何位置調用此方法,如在碰撞測試中,或在Update()或FixedUpdate()中。

FSMSystem.cs

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


/// <summary>
/// 轉換狀态
/// </summary>
public enum Transition
{
    NullTransition = 0,
    LostPlayer,
    SawPlayer,
}

/// <summary>
/// 狀态ID
/// </summary>
public enum StateID
{
    NullStateID = 0,
    FollowingPath,
    ChasingPlayer,
}

/// <summary>
/// 有限狀态機系統中的狀态
/// 每個狀态都有一個字典,字典中有鍵值對(轉換-狀态),儲存轉換Key時,把枚舉轉換為int,作為key
/// 表示如果在目前狀态下觸發轉換,那麼FSM應該處于對應的狀态。
/// </summary>
public abstract class FSMState
{
    protected Dictionary<int, StateID> m_Map = new Dictionary<int, StateID>();
    protected StateID stateID;
    public StateID ID { get { return stateID; } }

    /// <summary>
    /// 添加轉換
    /// </summary>
    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;
        }
        int transition = (int)trans;
        if (m_Map.ContainsKey(transition))
        {
            Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
                           "Impossible to assign to another state");
            return;
        }

        m_Map.Add(transition, id);
    }

    /// <summary>
    /// 删除轉換
    /// </summary>
    public void DeleteTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed");
            return;
        }
        int transition = (int)trans;
        if (m_Map.ContainsKey(transition))
        {
            m_Map.Remove(transition);
            return;
        }
        Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
                       " was not on the state's transition list");
    }

    /// <summary>
    /// 根據轉換傳回狀态ID
    /// </summary>
    public StateID GetOutputState(Transition trans)
    {
        int transition = (int)trans;
        if (m_Map.ContainsKey(transition))
        {
            return m_Map[transition];
        }
        return StateID.NullStateID;
    }

    /// <summary>
    /// 用于進入狀态前,設定進入的狀态條件
    /// 在進入目前狀态之前,FSM系統會自動調用
    /// </summary>
    public virtual void DoBeforeEntering() { }

    /// <summary>
    /// 用于離開狀态時的變量重置
    /// 在更改為新狀态之前,FSM系統會自動調用
    /// </summary>
    public virtual void DoBeforeLeaving() { }

    /// <summary>
    /// 用于判斷是否可以轉換到另一個狀态,每幀都會執行
    /// </summary>
    public abstract void CheckTransition(GameObject player, GameObject npc);

    /// <summary>
    /// 控制NPC行為,每幀都會執行
    /// </summary>
    public abstract void Act(GameObject player, GameObject npc);

}


/// <summary>
/// FSMSystem類
/// 持有一個狀态集合
/// </summary>
public class FSMSystem
{
    private List<FSMState> m_States;

    // 改變FSM狀态的唯一方式是觸發轉換
    // 不要直接改變狀态
    private StateID currentStateID;
    public StateID CurrentStateID { get { return currentStateID; } }
    private FSMState currentState;
    public FSMState CurrentState { get { return currentState; } }

    public FSMSystem()
    {
        m_States = new List<FSMState>();
    }

    /// <summary>
    /// 添加新的狀态
    /// </summary>
    public void AddState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError("FSM ERROR: Null reference is not allowed");
        }

        if (m_States.Count == 0)
        {
            m_States.Add(state);
            currentState = state;
            currentStateID = state.ID;
            return;
        }

        foreach (FSMState s in m_States)
        {
            if (s.ID == state.ID)
            {
                Debug.LogError("FSM ERROR: Impossible to add state " + state.ID.ToString() +
                    " because state has already been added");
                return;
            }
        }
        m_States.Add(state);
    }

    /// <summary>
    /// 删除狀态
    /// </summary>
    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 m_States)
        {
            if (state.ID == id)
            {
                m_States.Remove(state);
                return;
            }
        }
        Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
                       ". It was not on the list of states");
    }

    /// <summary>
    /// 通過轉換,改變FSM的狀态
    /// </summary>
    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
            return;
        }
        //擷取轉換對應的狀态ID
        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;
        }

        // 更新目前狀态ID,currentStateID		
        currentStateID = id;
        foreach (FSMState state in m_States)
        {
            if (state.ID == currentStateID)
            {
                // 離開狀态時的變量重置
                currentState.DoBeforeLeaving();
                // 更新目前狀态currentState
                currentState = state;
                // 進入狀态前,設定進入的狀态條件
                currentState.DoBeforeEntering();
                break;
            }
        }
    }
}
           

應用執行個體

在Unity下開發,如果目标距離帶有此腳本的GameObject小于一定的距離,GameObject将開始追蹤目标,否則,帶有此腳本的GameObject将按照路徑點巡邏。

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class NPCControl : MonoBehaviour
{
    public GameObject m_Player;
    public Transform[] m_Path;
    private FSMSystem m_FSM;

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

    public void Start()
    {
        MakeFSM();
    }

    public void FixedUpdate()
    {
        m_FSM.CurrentState.CheckTransition(m_Player, gameObject);
        m_FSM.CurrentState.Act(m_Player, gameObject);
    }

    // NPC有兩個狀态: FollowPath(沿着路徑巡邏) 和 ChasePlayer(追尋玩家)
    // 當在FollowPath狀态時,SawPlayer轉換被觸發時,将變為ChasingPlayer狀态
    // 當在ChasePlayer狀态時,LostPlayer轉換被觸發,将變為FollowPath狀态
    private void MakeFSM()
    {
        FollowPathState follow = new FollowPathState(m_Path);
        follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);

        ChasePlayerState chase = new ChasePlayerState();
        chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);

        m_FSM = new FSMSystem();
        m_FSM.AddState(follow);
        m_FSM.AddState(chase);
    }
}

/// <summary>
/// FollowPath(沿着路徑巡邏)
/// </summary>
public class FollowPathState : FSMState
{
    private int currentWayPoint;
    private Transform[] waypoints;

    public FollowPathState(Transform[] wp)
    {
        waypoints = wp;
        currentWayPoint = 0;
        stateID = StateID.FollowingPath;
    }

    public override void CheckTransition(GameObject player, GameObject npc)
    {
        //RaycastHit hit;
        //if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15f))
        //{
        //    if (hit.transform.gameObject.CompareTag("Player"))
        //        npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
        //}

        // 當Player距離NPC小于15米時,觸發SawPlayer狀态
        Collider[] colliders = Physics.OverlapSphere(npc.transform.position, 5f);
        if(colliders.Length <= 0)
        {
            return;
        }
        // 需要設定場景中Player的Tag為Player
        if (colliders[0].transform.gameObject.CompareTag("Player"))
            npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
    }

    public override void Act(GameObject player, GameObject npc)
    {
        // 沿着路徑點巡邏
        Rigidbody rigidbody = npc.GetComponent<Rigidbody>();
        Vector3 vel = rigidbody.velocity;
        // 計算移動方向
        Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
        // 如果距離小于1,前往下一個路徑點
        if (moveDir.magnitude < 1)
        {
            currentWayPoint++;
            if (currentWayPoint >= waypoints.Length)
            {
                currentWayPoint = 0;
            }
        }
        else
        {
            vel = moveDir.normalized * 10;
            // 面向路徑點
            npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
                                                      Quaternion.LookRotation(moveDir),
                                                      5 * Time.deltaTime);
            npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
        }
        rigidbody.velocity = vel;
    }

}

/// <summary>
/// ChasePlayer(追尋玩家)
/// </summary>
public class ChasePlayerState : FSMState
{
    public ChasePlayerState()
    {
        stateID = StateID.ChasingPlayer;
    }

    public override void CheckTransition(GameObject player, GameObject npc)
    {
        // 如果玩家距離NPC超出30米的距離,觸發LostPlayer轉換
        if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30)
            npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
    }

    public override void Act(GameObject player, GameObject npc)
    {
        Rigidbody rigidbody = npc.GetComponent<Rigidbody>();
        Vector3 vel = rigidbody.velocity;
        // 找到玩家的方向
        Vector3 moveDir = player.transform.position - npc.transform.position;

        // 面向路徑點
        npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
                                                  Quaternion.LookRotation(moveDir),
                                                  5 * Time.deltaTime);
        npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
        vel = moveDir.normalized * 10;
        rigidbody.velocity = vel;
    }

}
           

工程位址:

連結:https://pan.baidu.com/s/1H07NQYw-gqDOXWaWh-oFPw 

提取碼:dqse