首先從此連結上映入眼簾的是兩個腳本加一個例子,由于是全英文的,估計大部分人不願意碰這玩意,沒辦法,這就是瓶頸。如果你想更進一步的必須得越過這道坎,這就是核心競争力!不過現在你不讀也行,因為我會一步一步為您解刨這個狀态機系統的。我想我這是幫人還是害人呢?您認為呢?腳本如下:
FSMSystem.cs
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- public enum Transition
- {
- //定義了一個Transition(轉換)類型的枚舉變量,是以我們接下來要根據實際情況擴充此枚舉變量。
- NullTransition = 0,
- }
- public enum StateID
- {
- //定義了一個StateId(狀态ID)類型的枚舉變量,是以我們接下來也要根據實際情況擴充此枚舉變量。
- NullStateID = 0,
- }
- public abstract class FSMState//抽象類,我們必須繼承它才可以在腳本中執行個體化并使用它
- {
- protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
- protected StateID stateID;
- public StateID ID { get { return stateID; } }
- public void AddTransition(Transition trans, StateID id)//增加關聯對(轉換,狀态ID)
- {
- // Check if anyone of the args is invalid
- if (trans == Transition.NullTransition)//如果增加的轉換是個NullTransition(空轉換),直接Debug.LogError,然後傳回
- {
- Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
- return;
- }
- if (id == StateID.NullStateID)//如果狀态ID是NullStateID(空狀态ID),怎麼辦?還是Debug.LoError,然後傳回
- {
- Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
- return;
- }
- if (map.ContainsKey(trans))//如果将要增加的關聯對是之前就存在與關聯容器中,也照樣Debug.LogError,之後傳回被調用處
- {
- Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
- "Impossible to assign to another state");
- return;
- }
- map.Add(trans, id);//沖破了這些阻礙的話,終歸可以添加此關聯對了,下面的DeleteTransition函數就不用我寫注釋了吧!
- }
- 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)//此函數由下面這個腳本FSMSystem.cs中的PerformTransition函數調用。是用來檢索狀态的。
- {
- if (map.ContainsKey(trans))
- {
- return map[trans];
- }
- return StateID.NullStateID;
- }
- public virtual void DoBeforeEntering() { }//從名字就可以看出它的作用是什麼,但是我們得在FSMSystem.cs中得到答案。
- public virtual void DoBeforeLeaving() { }
- public abstract void Reason(GameObject player, GameObject npc);
- public abstract void Act(GameObject player, GameObject npc);
- }
複制代碼
FSMSystem.cs:
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- public class FSMSystem {
- private List<FSMState> states;//此類中植入一個類型為FSMState的List容器
- // The only way one can change the state of the FSM is by performing a transition
- //唯一你可以改變FSM中的狀态的方法是事先一個轉換,這樣講估計有點難以了解,不過我會通過例子來講解的。
- // Don't change the CurrentState directly 不要直接修改CurrentState的值。
- private StateID currentStateID ;
- public StateID CurrentStateID { get { return currentStateID; } }//記住,不要直接修改這個變量,之是以讓他公有是因為得讓其他腳本調用這個變量。
- private FSMState currentState;//記錄目前狀态
- public FSMState CurrentState { get { return currentState; } }//同上
- public FSMSystem()
- {
- states = new List<FSMState>();//執行個體化states。
- }
- 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);//這一句代碼第一次不執行,因為第一次states是空的,執行到上面的if裡面後立即傳回了
- }
- public void DeleteState(StateID id)//跟據ID來從容器states中定向移除FSMState執行個體
- {
- 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");
- }
- // Check if the currentState has the transition passed as argument
- StateID id = currentState.GetOutputState(trans);//這下我們得回到當初我所說講到的FSMState.cs中的那個檢索狀态的函數。如果檢索不出來,就傳回NullStateId,即執行下面if語句。
- if (id == StateID.NullStateID)
- {
- Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
- " for transition " + trans.ToString());
- return;
- }
- currentStateID = id;//還是那句話,如果查到了有這個狀态,那麼我們就将其指派給成員變量currentStateID。
- foreach (FSMState state in states)//周遊此狀态容器
- {
- if (state.ID == currentStateID)
- {
- currentState.DoBeforeLeaving();//我們在轉換之前或許要做點什麼吧!,是以我們如有需要,得在FSMState實作類中覆寫一下這個方法
- currentState = state;//好了,做完了轉換之前的預備工作(DoBeforeLeaving),是時候該轉換狀态了
- currentState.DoBeforeEntering();//狀态轉換完成之後,有可能得先為新狀态做點事吧,那麼我們也得DoBeforeEntering函數
- break;
- }
- }
- }
- }
複制代碼
我想大家對此腳本已有了一定的了解了,但是估計還不知道怎麼用吧!我給的連結上有一個Example例子,但是光看這個要想想熟練運用這個狀态機系統确實得花一番心思。是以我來一步一步地解剖這個例子:
- using System;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine;
- [RequireComponent(typeof(Rigidbody))]
- public class NPCControl : MonoBehaviour
- {
- public GameObject player;//主角
- public Transform[] path;//多個尋路點
- private FSMSystem fsm;//内置一個fsm
- public void SetTransition(Transition t) //轉換狀态
- {
- fsm.PerformTransition(t);
- }
- public void Start()
- {
- MakeFSM();//首先初始化狀态機,執行MakeFSM函數
- }
- public void FixedUpdate()//作為驅動源
- {
- fsm.CurrentState.Reason(player, gameObject);//定期(預設是0.02秒,在Edit->rojectSetting->Time中可以發現)調用目前FSMState中的Reason函數,用以檢測外界環境是否發生變化,并且根據發生的變化來執行某些事件
- fsm.CurrentState.Act(player, gameObject);//定期執行目前狀态下的某些行為
- }
- // The NPC has two states: FollowPath and ChasePlayer
- // If it's on the first state and SawPlayer transition is fired, it changes to ChasePlayer
- // If it's on ChasePlayerState and LostPlayer transition is fired, it returns to FollowPath
- private void MakeFSM()
- {
- FollowPathState follow = new FollowPathState(path);//定義并執行個體化FSMState
- follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);//向其添加轉換對
- ChasePlayerState chase = new ChasePlayerState();
- chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
- //我畫一張圖,你們就明白了這句話了:
複制代碼
那個實心的箭頭代表的代碼就是上面圓角矩形裡面的代碼。看了之後我們因該明白了那兩句代碼的現實意義了吧!即定義轉換,也就是floow狀态可以與chase互相轉換,如果我們填充的狀态中出現了别的狀态比如說:state0,此時狀态floow就不能轉換到state0了,同樣state0也無法轉換到floow。
***************************************************
- fsm = new FSMSystem();//執行個體化fsm
- fsm.AddState(follow);//将follow裝載到fsm中
- fsm.AddState(chase);//将chase裝載到fsm中
- }
- }
- public class FollowPathState : FSMState
- {
- private int currentWayPoint;
- private Transform[] waypoints;
- public FollowPathState(Transform[] wp)
- {
- waypoints = wp;
- currentWayPoint = 0;
- stateID = StateID.FollowingPath;
- }
- public override void Reason(GameObject player, GameObject npc)
- {
- // If the Player passes less than 15 meters away in front of the NPC
- RaycastHit hit;
- if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F))
- {
- if (hit.transform.gameObject.tag == "player")
- npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);//當射線射到的物體的标簽為Player時,觸發轉換。
- }
- }
- public override void Act(GameObject player, GameObject npc)//當NPC目前狀态為follow時不斷執行以下行為。下面那個類的用法也是一樣的。
- {
- // Follow the path of waypoints
- // Find the direction of the current way point
- Vector3 vel = npc.rigidbody.velocity;
- Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
- if (moveDir.magnitude < 1)
- {
- currentWayPoint++;
- if (currentWayPoint >= waypoints.Length)
- {
- currentWayPoint = 0;
- }
- }
- else
- {
- vel = moveDir.normalized * 10;
- // Rotate towards the waypoint
- 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);
- }
- // Apply the Velocity
- npc.rigidbody.velocity = vel;
- }
- } // FollowPathState
- public class ChasePlayerState : FSMState//同上。
- {
- public ChasePlayerState()
- {
- stateID = StateID.ChasingPlayer;
- }
- public override void Reason(GameObject player, GameObject npc)
- {
- // If the player has gone 30 meters away from the NPC, fire LostPlayer transition
- if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30)
- npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
- }
- public override void Act(GameObject player, GameObject npc)
- {
- // Follow the path of waypoints
- // Find the direction of the player
- Vector3 vel = npc.rigidbody.velocity;
- Vector3 moveDir = player.transform.position - npc.transform.position;
- // Rotate towards the waypoint
- 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;
- // Apply the new Velocity
- npc.rigidbody.velocity = vel;
- }
- }
複制代碼
我來總結一下,此狀态機架構的用法如下:首先我們得填充抽象類FSMState中的兩個枚舉類型,然後針對具體情況繼承此抽象類并設計腳本,且腳本中必須有一個FSMSystem類型成員變量(可以仿照上面的例子),并且要在Update或FIxedUpdate等函數中不斷驅動此狀态機運作。且首先我們得用一些FSMState執行個體來裝載此狀态機系統執行個體。而且我們得對每一個FSMState執行個體添加轉換對,控制該狀态轉換的方向。最後在每個FSMState子類中覆寫Reson與Act函數。其中Reson是監聽外界條件變化的并且執行某些轉換,而Act是表現目前狀态行為的函數。
了解了這些,你還覺得自己不會用這個FSMSystem嗎?多用用就好了,下次見!
原文連結:http://www.narkii.com/club/thread-272375-1.html