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.     /*这个成员变量是一个Dictionary类型,就相当于java中的Map类型,存储的是一个个的关联对。此刻我们存储的关联对类型就是上面我们定义的连个枚举类型。那么接下来我们猜也能才出来我们一定会向其添加关联对,可能还会移除此关联对。那么这个东西的用处我们现在还是很迷茫,不要紧,继续向下看吧!没问题的。*/
  18.     protected StateID stateID;
  19.     public StateID ID { get { return stateID; } }
  20.     public void AddTransition(Transition trans, StateID id)//增加关联对(转换,状态ID)
  21.     {
  22.         // Check if anyone of the args is invalid
  23.         if (trans == Transition.NullTransition)//如果增加的转换是个NullTransition(空转换),直接Debug.LogError,然后返回
  24.         {
  25.             Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
  26.             return;
  27.         }
  28.         if (id == StateID.NullStateID)//如果状态ID是NullStateID(空状态ID),怎么办?还是Debug.LoError,然后返回
  29.         {
  30.             Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
  31.             return;
  32.         }
  33.         if (map.ContainsKey(trans))//如果将要增加的关联对是之前就存在与关联容器中,也照样Debug.LogError,之后返回被调用处
  34.         {
  35.             Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
  36.                        "Impossible to assign to another state");
  37.             return;
  38.         }
  39.         map.Add(trans, id);//冲破了这些阻碍的话,终归可以添加此关联对了,下面的DeleteTransition函数就不用我写注释了吧!
  40.     }
  41.     public void DeleteTransition(Transition trans)//删除关联对函数,前提是里面要有这个关联对啊!
  42.     {
  43.         if (trans == Transition.NullTransition)
  44.         {
  45.             Debug.LogError("FSMState ERROR: NullTransition is not allowed");
  46.             return;
  47.         }
  48.         if (map.ContainsKey(trans))
  49.         {
  50.             map.Remove(trans);
  51.             return;
  52.         }
  53.         Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
  54.                        " was not on the state's transition list");
  55.     }

  56.     public StateID GetOutputState(Transition trans)//此函数由下面这个脚本FSMSystem.cs中的PerformTransition函数调用。是用来检索状态的。
  57.     {
  58.         if (map.ContainsKey(trans))
  59.         {
  60.             return map[trans];
  61.         }
  62.         return StateID.NullStateID;
  63.     }
  64.     public virtual void DoBeforeEntering() { }//从名字就可以看出它的作用是什么,但是我们得在FSMSystem.cs中得到答案。
  65.     public virtual void DoBeforeLeaving() { }
  66.     public abstract void Reason(GameObject player, GameObject npc);
  67.     /*这个函数与下面这个函数是这个类中最重要的函数。Reason函数负责监听环境条件的改变并触发相应的事件转换。Act函数的作用在于表现当前状态下NPC的行为。我们得在这个抽象类的子类中覆写这两个方法。
  68.         
  69. */    
  70.     public abstract void Act(GameObject player, GameObject npc);

  71. }
复制代码




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.         
  20. if (s == null)

  21.         {
  22.             Debug.LogError("FSM ERROR: Null reference is not allowed");
  23.         }

  24.        if (states.Count == 0)/*第一次添加时必定执行这块代码,因为一开始states是空的,并且这块代码设置了第一次添加的状态是
  25. 默认的当前状态。这一点读者一定要理解,不然对于后面的东西读者会非常困惑的,因为其他地方没有地方设置运行后默认的当前状态。*/

  26.         {
  27.             states.Add(s);
  28.             currentState = s;
  29.             currentStateID = s.ID;//这里实例化了这两个成员变量
  30.             return;
  31.         }

  32.          foreach (FSMState state in states)//排除相同的状态
  33.         {
  34.             if (state.ID == s.ID)
  35.             {
  36.                 Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
  37.                                " because state has already been added");
  38.                 return;
  39.             }
  40.         }
  41.         states.Add(s);//这一句代码第一次不执行,因为第一次states是空的,执行到上面的if里面后立即返回了
  42.     }

  43.    
  44.     public void DeleteState(StateID id)//跟据ID来从容器states中定向移除FSMState实例
  45.     {
  46.         
  47. if (id == StateID.NullStateID)

  48.         {
  49.             Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
  50.             return;
  51.         }

  52.        
  53. foreach (FSMState state in states)

  54.         {
  55.             if (state.ID == id)
  56.             {
  57.                 states.Remove(state);
  58.                 return;
  59.             }
  60.         }
  61.         Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
  62.                        ". It was not on the list of states");
  63.     }

  64.     public void PerformTransition(Transition trans)//执行转换
  65.     {
  66.         
  67. if (trans == Transition.NullTransition)

  68.         {
  69.             Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
  70.             
  71.         }

  72.         // Check if the currentState has the transition passed as argument
  73.         StateID id = currentState.GetOutputState(trans);//这下我们得回到当初我所说讲到的FSMState.cs中的那个检索状态的函数。如果检索不出来,就返回NullStateId,即执行下面if语句。
  74.         if (id == StateID.NullStateID)
  75.         {
  76.             Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
  77.                            " for transition " + trans.ToString());
  78.             return;
  79.         }

  80.        
  81. currentStateID = id;//还是那句话,如果查到了有这个状态,那么我们就将其赋值给成员变量currentStateID。

  82.         foreach (FSMState state in states)//遍历此状态容器
  83.         {
  84.             if (state.ID == currentStateID)
  85.             {
  86.                
  87. currentState.DoBeforeLeaving();//我们在转换之前或许要做点什么吧!,所以我们如有需要,得在FSMState实现类中覆写一下这个方法


  88.                 currentState = state;//好了,做完了转换之前的预备工作(DoBeforeLeaving),是时候该转换状态了

  89.                
  90. currentState.DoBeforeEntering();//状态转换完成之后,有可能得先为新状态做点事吧,那么我们也得DoBeforeEntering函数

  91.                 break;
  92.             }
  93.         }
  94.      
  95.     } 
  96. }
复制代码


我想大家对此脚本已有了一定的理解了,但是估计还不知道怎么用吧!我给的链接上有一个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.         

  32. ChasePlayerState chase = new ChasePlayerState();


  33.         chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
  34.         //我画一张图,你们就明白了这句话了:
复制代码
unity 有限状态机_第1张图片 


那个实心的箭头代表的代码就是上面圆角矩形里面的代码。看了之后我们因该明白了那两句代码的现实意义了吧!即定义转换,也就是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. /*继承抽象类FSMState,但是得注意一点:我们得在抽象类FSMState脚本中的两个枚举变量分别加入对应的枚举变量,比如在Transition中加入SawPlayer,LostPlayer;在StateID中加入ChasingPlayer,FollowingPath。*/
  8. {
  9.     private int currentWayPoint;
  10.     private Transform[] waypoints;

  11.     public FollowPathState(Transform[] wp) 
  12.     { 
  13.         waypoints = wp;
  14.         currentWayPoint = 0;
  15.         stateID = StateID.FollowingPath;
  16.     }

  17.     public override void Reason(GameObject player, GameObject npc)
  18.     {
  19.         // If the Player passes less than 15 meters away in front of the NPC
  20.         RaycastHit hit;
  21.         if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F))
  22.         {
  23.             if (hit.transform.gameObject.tag == "player")
  24.                 npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);//当射线射到的物体的标签为Player时,触发转换。
  25.         }
  26.     }

  27.     public override void Act(GameObject player, GameObject npc)//当NPC当前状态为follow时不断执行以下行为。下面那个类的用法也是一样的。
  28.     {
  29.         // Follow the path of waypoints
  30.                 // Find the direction of the current way point 
  31.         Vector3 vel = npc.rigidbody.velocity;
  32.         Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;

  33.         if (moveDir.magnitude < 1)
  34.         {
  35.             currentWayPoint++;
  36.             if (currentWayPoint >= waypoints.Length)
  37.             {
  38.                 currentWayPoint = 0;
  39.             }
  40.         }
  41.         else
  42.         {
  43.             vel = moveDir.normalized * 10;

  44.             // Rotate towards the waypoint
  45.             npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
  46.                                                       Quaternion.LookRotation(moveDir),
  47.                                                       5 * Time.deltaTime);
  48.             npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);

  49.         }

  50.         // Apply the Velocity
  51.         npc.rigidbody.velocity = vel;
  52.     }

  53. } // FollowPathState

  54. public class ChasePlayerState : FSMState//同上。
  55. {
  56.     public ChasePlayerState()
  57.     {
  58.         stateID = StateID.ChasingPlayer;
  59.     }

  60.     public override void Reason(GameObject player, GameObject npc)
  61.     {
  62.         // If the player has gone 30 meters away from the NPC, fire LostPlayer transition
  63.         if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30)
  64.             npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
  65.     }

  66.     public override void Act(GameObject player, GameObject npc)
  67.     {
  68.         // Follow the path of waypoints
  69.                 // Find the direction of the player                 
  70.         Vector3 vel = npc.rigidbody.velocity;
  71.         Vector3 moveDir = player.transform.position - npc.transform.position;

  72.         // Rotate towards the waypoint
  73.         npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
  74.                                                   Quaternion.LookRotation(moveDir),
  75.                                                   5 * Time.deltaTime);
  76.         npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);

  77.         vel = moveDir.normalized * 10;

  78.         // Apply the new Velocity
  79.         npc.rigidbody.velocity = vel;
  80.     }

  81. }
复制代码

我来总结一下,此状态机框架的用法如下:首先我们得填充抽象类FSMState中的两个枚举类型,然后针对具体情况继承此抽象类并设计脚本,且脚本中必须有一个FSMSystem类型成员变量(可以仿照上面的例子),并且要在Update或FIxedUpdate等函数中不断驱动此状态机运行。且首先我们得用一些FSMState实例来装载此状态机系统实例。而且我们得对每一个FSMState实例添加转换对,控制该状态转换的方向。最后在每个FSMState子类中覆写Reson与Act函数。其中Reson是监听外界条件变化的并且执行某些转换,而Act是表现当前状态行为的函数。


了解了这些,你还觉得自己不会用这个FSMSystem吗?多用用就好了,下次见!


原文链接:http://www.narkii.com/club/thread-272375-1.html

你可能感兴趣的:(unity 有限状态机)