天天看點

unity 有限狀态機

首先從此連結上映入眼簾的是兩個腳本加一個例子,由于是全英文的,估計大部分人不願意碰這玩意,沒辦法,這就是瓶頸。如果你想更進一步的必須得越過這道坎,這就是核心競争力!不過現在你不讀也行,因為我會一步一步為您解刨這個狀态機系統的。我想我這是幫人還是害人呢?您認為呢?腳本如下:

FSMSystem.cs

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public enum Transition
  5. {
  6. //定義了一個Transition(轉換)類型的枚舉變量,是以我們接下來要根據實際情況擴充此枚舉變量。
  7.     NullTransition = 0,
  8. }
  9. public enum StateID
  10. {
  11. //定義了一個StateId(狀态ID)類型的枚舉變量,是以我們接下來也要根據實際情況擴充此枚舉變量。
  12.     NullStateID = 0,
  13. }
  14. public abstract class FSMState//抽象類,我們必須繼承它才可以在腳本中執行個體化并使用它
  15. {
  16.     protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
  17.     protected StateID stateID;
  18.     public StateID ID { get { return stateID; } }
  19.     public void AddTransition(Transition trans, StateID id)//增加關聯對(轉換,狀态ID)
  20.     {
  21.         // Check if anyone of the args is invalid
  22.         if (trans == Transition.NullTransition)//如果增加的轉換是個NullTransition(空轉換),直接Debug.LogError,然後傳回
  23.         {
  24.             Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
  25.             return;
  26.         }
  27.         if (id == StateID.NullStateID)//如果狀态ID是NullStateID(空狀态ID),怎麼辦?還是Debug.LoError,然後傳回
  28.         {
  29.             Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
  30.             return;
  31.         }
  32.         if (map.ContainsKey(trans))//如果将要增加的關聯對是之前就存在與關聯容器中,也照樣Debug.LogError,之後傳回被調用處
  33.         {
  34.             Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
  35.                        "Impossible to assign to another state");
  36.             return;
  37.         }
  38.         map.Add(trans, id);//沖破了這些阻礙的話,終歸可以添加此關聯對了,下面的DeleteTransition函數就不用我寫注釋了吧!
  39.     }
  40.     public void DeleteTransition(Transition trans)//删除關聯對函數,前提是裡面要有這個關聯對啊!
  41.     {
  42.         if (trans == Transition.NullTransition)
  43.         {
  44.             Debug.LogError("FSMState ERROR: NullTransition is not allowed");
  45.             return;
  46.         }
  47.         if (map.ContainsKey(trans))
  48.         {
  49.             map.Remove(trans);
  50.             return;
  51.         }
  52.         Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
  53.                        " was not on the state's transition list");
  54.     }
  55.     public StateID GetOutputState(Transition trans)//此函數由下面這個腳本FSMSystem.cs中的PerformTransition函數調用。是用來檢索狀态的。
  56.     {
  57.         if (map.ContainsKey(trans))
  58.         {
  59.             return map[trans];
  60.         }
  61.         return StateID.NullStateID;
  62.     }
  63.     public virtual void DoBeforeEntering() { }//從名字就可以看出它的作用是什麼,但是我們得在FSMSystem.cs中得到答案。
  64.     public virtual void DoBeforeLeaving() { }
  65.     public abstract void Reason(GameObject player, GameObject npc);
  66.     public abstract void Act(GameObject player, GameObject npc);
  67. }

複制代碼

FSMSystem.cs:

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public class FSMSystem {
  5.     private List<FSMState> states;//此類中植入一個類型為FSMState的List容器
  6.     // The only way one can change the state of the FSM is by performing a transition
  7.     //唯一你可以改變FSM中的狀态的方法是事先一個轉換,這樣講估計有點難以了解,不過我會通過例子來講解的。
  8.     // Don't change the CurrentState directly  不要直接修改CurrentState的值。
  9.     private StateID currentStateID ;
  10.     public StateID CurrentStateID { get { return currentStateID; } }//記住,不要直接修改這個變量,之是以讓他公有是因為得讓其他腳本調用這個變量。
  11.     private FSMState currentState;//記錄目前狀态
  12.     public FSMState CurrentState { get { return currentState; } }//同上
  13.     public FSMSystem()
  14.     {
  15.         states = new List<FSMState>();//執行個體化states。
  16.     }
  17.     public void AddState(FSMState s)//增加狀态轉換對
  18.     {
  19. if (s == null)
  20.         {
  21.             Debug.LogError("FSM ERROR: Null reference is not allowed");
  22.         }
  23.        if (states.Count == 0)
  24.         {
  25.             states.Add(s);
  26.             currentState = s;
  27.             currentStateID = s.ID;//這裡執行個體化了這兩個成員變量
  28.             return;
  29.         }
  30.          foreach (FSMState state in states)//排除相同的狀态
  31.         {
  32.             if (state.ID == s.ID)
  33.             {
  34.                 Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
  35.                                " because state has already been added");
  36.                 return;
  37.             }
  38.         }
  39.         states.Add(s);//這一句代碼第一次不執行,因為第一次states是空的,執行到上面的if裡面後立即傳回了
  40.     }
  41.     public void DeleteState(StateID id)//跟據ID來從容器states中定向移除FSMState執行個體
  42.     {
  43. if (id == StateID.NullStateID)
  44.         {
  45.             Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
  46.             return;
  47.         }
  48. foreach (FSMState state in states)
  49.         {
  50.             if (state.ID == id)
  51.             {
  52.                 states.Remove(state);
  53.                 return;
  54.             }
  55.         }
  56.         Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
  57.                        ". It was not on the list of states");
  58.     }
  59.     public void PerformTransition(Transition trans)//執行轉換
  60.     {
  61. if (trans == Transition.NullTransition)
  62.         {
  63.             Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
  64.         }
  65.         // Check if the currentState has the transition passed as argument
  66.         StateID id = currentState.GetOutputState(trans);//這下我們得回到當初我所說講到的FSMState.cs中的那個檢索狀态的函數。如果檢索不出來,就傳回NullStateId,即執行下面if語句。
  67.         if (id == StateID.NullStateID)
  68.         {
  69.             Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
  70.                            " for transition " + trans.ToString());
  71.             return;
  72.         }
  73. currentStateID = id;//還是那句話,如果查到了有這個狀态,那麼我們就将其指派給成員變量currentStateID。
  74.         foreach (FSMState state in states)//周遊此狀态容器
  75.         {
  76.             if (state.ID == currentStateID)
  77.             {
  78. currentState.DoBeforeLeaving();//我們在轉換之前或許要做點什麼吧!,是以我們如有需要,得在FSMState實作類中覆寫一下這個方法
  79.                 currentState = state;//好了,做完了轉換之前的預備工作(DoBeforeLeaving),是時候該轉換狀态了
  80. currentState.DoBeforeEntering();//狀态轉換完成之後,有可能得先為新狀态做點事吧,那麼我們也得DoBeforeEntering函數
  81.                 break;
  82.             }
  83.         }
  84.     } 
  85. }

複制代碼

我想大家對此腳本已有了一定的了解了,但是估計還不知道怎麼用吧!我給的連結上有一個Example例子,但是光看這個要想想熟練運用這個狀态機系統确實得花一番心思。是以我來一步一步地解剖這個例子:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using UnityEngine;
  5. [RequireComponent(typeof(Rigidbody))]
  6. public class NPCControl : MonoBehaviour
  7. {
  8.     public GameObject player;//主角
  9.     public Transform[] path;//多個尋路點
  10.     private FSMSystem fsm;//内置一個fsm
  11.     public void SetTransition(Transition t) //轉換狀态
  12.     {
  13.          fsm.PerformTransition(t);
  14.      }
  15.     public void Start()
  16.     {
  17.         MakeFSM();//首先初始化狀态機,執行MakeFSM函數
  18.     }
  19.     public void FixedUpdate()//作為驅動源
  20.     {
  21.         fsm.CurrentState.Reason(player, gameObject);//定期(預設是0.02秒,在Edit->rojectSetting->Time中可以發現)調用目前FSMState中的Reason函數,用以檢測外界環境是否發生變化,并且根據發生的變化來執行某些事件
  22.         fsm.CurrentState.Act(player, gameObject);//定期執行目前狀态下的某些行為
  23.     }
  24.         // The NPC has two states: FollowPath and ChasePlayer
  25.         // If it's on the first state and SawPlayer transition is fired, it changes to ChasePlayer
  26.         // If it's on ChasePlayerState and LostPlayer transition is fired, it returns to FollowPath
  27.     private void MakeFSM()
  28.     {
  29.         FollowPathState follow = new FollowPathState(path);//定義并執行個體化FSMState
  30.         follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);//向其添加轉換對
  31. ChasePlayerState chase = new ChasePlayerState();
  32.         chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
  33.         //我畫一張圖,你們就明白了這句話了:

複制代碼

unity 有限狀态機

那個實心的箭頭代表的代碼就是上面圓角矩形裡面的代碼。看了之後我們因該明白了那兩句代碼的現實意義了吧!即定義轉換,也就是floow狀态可以與chase互相轉換,如果我們填充的狀态中出現了别的狀态比如說:state0,此時狀态floow就不能轉換到state0了,同樣state0也無法轉換到floow。

***************************************************

  1. fsm = new FSMSystem();//執行個體化fsm
  2.         fsm.AddState(follow);//将follow裝載到fsm中
  3.         fsm.AddState(chase);//将chase裝載到fsm中
  4.     }
  5. }
  6. public class FollowPathState : FSMState
  7. {
  8.     private int currentWayPoint;
  9.     private Transform[] waypoints;
  10.     public FollowPathState(Transform[] wp) 
  11.     { 
  12.         waypoints = wp;
  13.         currentWayPoint = 0;
  14.         stateID = StateID.FollowingPath;
  15.     }
  16.     public override void Reason(GameObject player, GameObject npc)
  17.     {
  18.         // If the Player passes less than 15 meters away in front of the NPC
  19.         RaycastHit hit;
  20.         if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F))
  21.         {
  22.             if (hit.transform.gameObject.tag == "player")
  23.                 npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);//當射線射到的物體的标簽為Player時,觸發轉換。
  24.         }
  25.     }
  26.     public override void Act(GameObject player, GameObject npc)//當NPC目前狀态為follow時不斷執行以下行為。下面那個類的用法也是一樣的。
  27.     {
  28.         // Follow the path of waypoints
  29.                 // Find the direction of the current way point 
  30.         Vector3 vel = npc.rigidbody.velocity;
  31.         Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
  32.         if (moveDir.magnitude < 1)
  33.         {
  34.             currentWayPoint++;
  35.             if (currentWayPoint >= waypoints.Length)
  36.             {
  37.                 currentWayPoint = 0;
  38.             }
  39.         }
  40.         else
  41.         {
  42.             vel = moveDir.normalized * 10;
  43.             // Rotate towards the waypoint
  44.             npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
  45.                                                       Quaternion.LookRotation(moveDir),
  46.                                                       5 * Time.deltaTime);
  47.             npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
  48.         }
  49.         // Apply the Velocity
  50.         npc.rigidbody.velocity = vel;
  51.     }
  52. } // FollowPathState
  53. public class ChasePlayerState : FSMState//同上。
  54. {
  55.     public ChasePlayerState()
  56.     {
  57.         stateID = StateID.ChasingPlayer;
  58.     }
  59.     public override void Reason(GameObject player, GameObject npc)
  60.     {
  61.         // If the player has gone 30 meters away from the NPC, fire LostPlayer transition
  62.         if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30)
  63.             npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
  64.     }
  65.     public override void Act(GameObject player, GameObject npc)
  66.     {
  67.         // Follow the path of waypoints
  68.                 // Find the direction of the player                 
  69.         Vector3 vel = npc.rigidbody.velocity;
  70.         Vector3 moveDir = player.transform.position - npc.transform.position;
  71.         // Rotate towards the waypoint
  72.         npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
  73.                                                   Quaternion.LookRotation(moveDir),
  74.                                                   5 * Time.deltaTime);
  75.         npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
  76.         vel = moveDir.normalized * 10;
  77.         // Apply the new Velocity
  78.         npc.rigidbody.velocity = vel;
  79.     }
  80. }

複制代碼

我來總結一下,此狀态機架構的用法如下:首先我們得填充抽象類FSMState中的兩個枚舉類型,然後針對具體情況繼承此抽象類并設計腳本,且腳本中必須有一個FSMSystem類型成員變量(可以仿照上面的例子),并且要在Update或FIxedUpdate等函數中不斷驅動此狀态機運作。且首先我們得用一些FSMState執行個體來裝載此狀态機系統執行個體。而且我們得對每一個FSMState執行個體添加轉換對,控制該狀态轉換的方向。最後在每個FSMState子類中覆寫Reson與Act函數。其中Reson是監聽外界條件變化的并且執行某些轉換,而Act是表現目前狀态行為的函數。

了解了這些,你還覺得自己不會用這個FSMSystem嗎?多用用就好了,下次見!

原文連結:http://www.narkii.com/club/thread-272375-1.html