zoukankan      html  css  js  c++  java
  • 基于Unity有限状态机框架

       这个框架是Unity wiki上的框架。网址:http://wiki.unity3d.com/index.php/Finite_State_Machine

       这就相当于是“模板”吧,自己写的代码,写啥都是一个风格,还是得多读书,多看报啊...

     有限状态机用来做状态转移,比如代码中的例子,NPC有两个状态跟着主角走(状态A),或者沿着自己的路线走(状态B)。有两个转换,其实就是两条边,A->B,B->A.

     框架里面Transition表示边,StateID表示状态,FSMState是一个状态的抽象类,最主要的是Reason方法,就是判断是不是要进行转换,像例子里B状态里写的就是如果npc前面15m有主角,就进行转换,转换的时候又要进行一些个操作。Act方法就是当前状态要做的一些事情。

     具体也没用过这个框架,也不知道理解的对不对。。项目里,我都是硬写乱搞的,网上搜了搜说,有限状态机有很多缺点,我觉得简单的只有几个状态的话,也挺方便的,以后可以尝试一下。

    框架:

      1 using System;
      2 using System.Collections;
      3 using System.Collections.Generic;
      4 using UnityEngine;
      5  
      6 /**
      7 A Finite State Machine System based on Chapter 3.1 of Game Programming Gems 1 by Eric Dybsand
      8  
      9 Written by Roberto Cezar Bianchini, July 2010
     10  
     11  
     12 How to use:
     13     1. Place the labels for the transitions and the states of the Finite State System
     14         in the corresponding enums.
     15  
     16     2. Write new class(es) inheriting from FSMState and fill each one with pairs (transition-state).
     17         These pairs represent the state S2 the FSMSystem should be if while being on state S1, a
     18         transition T is fired and state S1 has a transition from it to S2. Remember this is a Deterministic FSM. 
     19         You can't have one transition leading to two different states.
     20  
     21        Method Reason is used to determine which transition should be fired.
     22        You can write the code to fire transitions in another place, and leave this method empty if you
     23        feel it's more appropriate to your project.
     24  
     25        Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
     26        You can write the code for the actions in another place, and leave this method empty if you
     27        feel it's more appropriate to your project.
     28  
     29     3. Create an instance of FSMSystem class and add the states to it.
     30  
     31     4. Call Reason and Act (or whichever methods you have for firing transitions and making the NPCs
     32          behave in your game) from your Update or FixedUpdate methods.
     33  
     34     Asynchronous transitions from Unity Engine, like OnTriggerEnter, SendMessage, can also be used, 
     35     just call the Method PerformTransition from your FSMSystem instance with the correct Transition 
     36     when the event occurs.
     37  
     38  
     39  
     40 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
     41 INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
     42 AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
     43 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
     44 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     45 */
     46  
     47  
     48 /// <summary>
     49 /// Place the labels for the Transitions in this enum.
     50 /// Don't change the first label, NullTransition as FSMSystem class uses it.
     51 /// </summary>
     52 public enum Transition
     53 {
     54     NullTransition = 0, // Use this transition to represent a non-existing transition in your system
     55 }
     56  
     57 /// <summary>
     58 /// Place the labels for the States in this enum.
     59 /// Don't change the first label, NullTransition as FSMSystem class uses it.
     60 /// </summary>
     61 public enum StateID
     62 {
     63     NullStateID = 0, // Use this ID to represent a non-existing State in your system    
     64 }
     65  
     66 /// <summary>
     67 /// This class represents the States in the Finite State System.
     68 /// Each state has a Dictionary with pairs (transition-state) showing
     69 /// which state the FSM should be if a transition is fired while this state
     70 /// is the current state.
     71 /// Method Reason is used to determine which transition should be fired .
     72 /// Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
     73 /// </summary>
     74 public abstract class FSMState
     75 {
     76     protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
     77     protected StateID stateID;
     78     public StateID ID { get { return stateID; } }
     79  
     80     public void AddTransition(Transition trans, StateID id)
     81     {
     82         // Check if anyone of the args is invalid
     83         if (trans == Transition.NullTransition)
     84         {
     85             Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
     86             return;
     87         }
     88  
     89         if (id == StateID.NullStateID)
     90         {
     91             Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
     92             return;
     93         }
     94  
     95         // Since this is a Deterministic FSM,
     96         //   check if the current transition was already inside the map
     97         if (map.ContainsKey(trans))
     98         {
     99             Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() + 
    100                            "Impossible to assign to another state");
    101             return;
    102         }
    103  
    104         map.Add(trans, id);
    105     }
    106  
    107     /// <summary>
    108     /// This method deletes a pair transition-state from this state's map.
    109     /// If the transition was not inside the state's map, an ERROR message is printed.
    110     /// </summary>
    111     public void DeleteTransition(Transition trans)
    112     {
    113         // Check for NullTransition
    114         if (trans == Transition.NullTransition)
    115         {
    116             Debug.LogError("FSMState ERROR: NullTransition is not allowed");
    117             return;
    118         }
    119  
    120         // Check if the pair is inside the map before deleting
    121         if (map.ContainsKey(trans))
    122         {
    123             map.Remove(trans);
    124             return;
    125         }
    126         Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() + 
    127                        " was not on the state's transition list");
    128     }
    129  
    130     /// <summary>
    131     /// This method returns the new state the FSM should be if
    132     ///    this state receives a transition and 
    133     /// </summary>
    134     public StateID GetOutputState(Transition trans)
    135     {
    136         // Check if the map has this transition
    137         if (map.ContainsKey(trans))
    138         {
    139             return map[trans];
    140         }
    141         return StateID.NullStateID;
    142     }
    143  
    144     /// <summary>
    145     /// This method is used to set up the State condition before entering it.
    146     /// It is called automatically by the FSMSystem class before assigning it
    147     /// to the current state.
    148     /// </summary>
    149     public virtual void DoBeforeEntering() { }
    150  
    151     /// <summary>
    152     /// This method is used to make anything necessary, as reseting variables
    153     /// before the FSMSystem changes to another one. It is called automatically
    154     /// by the FSMSystem before changing to a new state.
    155     /// </summary>
    156     public virtual void DoBeforeLeaving() { } 
    157  
    158     /// <summary>
    159     /// This method decides if the state should transition to another on its list
    160     /// NPC is a reference to the object that is controlled by this class
    161     /// </summary>
    162     public abstract void Reason(GameObject player, GameObject npc);
    163  
    164     /// <summary>
    165     /// This method controls the behavior of the NPC in the game World.
    166     /// Every action, movement or communication the NPC does should be placed here
    167     /// NPC is a reference to the object that is controlled by this class
    168     /// </summary>
    169     public abstract void Act(GameObject player, GameObject npc);
    170  
    171 } // class FSMState
    172  
    173  
    174 /// <summary>
    175 /// FSMSystem class represents the Finite State Machine class.
    176 ///  It has a List with the States the NPC has and methods to add,
    177 ///  delete a state, and to change the current state the Machine is on.
    178 /// </summary>
    179 public class FSMSystem
    180 {
    181     private List<FSMState> states;
    182  
    183     // The only way one can change the state of the FSM is by performing a transition
    184     // Don't change the CurrentState directly
    185     private StateID currentStateID;
    186     public StateID CurrentStateID { get { return currentStateID; } }
    187     private FSMState currentState;
    188     public FSMState CurrentState { get { return currentState; } }
    189  
    190     public FSMSystem()
    191     {
    192         states = new List<FSMState>();
    193     }
    194  
    195     /// <summary>
    196     /// This method places new states inside the FSM,
    197     /// or prints an ERROR message if the state was already inside the List.
    198     /// First state added is also the initial state.
    199     /// </summary>
    200     public void AddState(FSMState s)
    201     {
    202         // Check for Null reference before deleting
    203         if (s == null)
    204         {
    205             Debug.LogError("FSM ERROR: Null reference is not allowed");
    206         }
    207  
    208         // First State inserted is also the Initial state,
    209         //   the state the machine is in when the simulation begins
    210         if (states.Count == 0)
    211         {
    212             states.Add(s);
    213             currentState = s;
    214             currentStateID = s.ID;
    215             return;
    216         }
    217  
    218         // Add the state to the List if it's not inside it
    219         foreach (FSMState state in states)
    220         {
    221             if (state.ID == s.ID)
    222             {
    223                 Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() + 
    224                                " because state has already been added");
    225                 return;
    226             }
    227         }
    228         states.Add(s);
    229     }
    230  
    231     /// <summary>
    232     /// This method delete a state from the FSM List if it exists, 
    233     ///   or prints an ERROR message if the state was not on the List.
    234     /// </summary>
    235     public void DeleteState(StateID id)
    236     {
    237         // Check for NullState before deleting
    238         if (id == StateID.NullStateID)
    239         {
    240             Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
    241             return;
    242         }
    243  
    244         // Search the List and delete the state if it's inside it
    245         foreach (FSMState state in states)
    246         {
    247             if (state.ID == id)
    248             {
    249                 states.Remove(state);
    250                 return;
    251             }
    252         }
    253         Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() + 
    254                        ". It was not on the list of states");
    255     }
    256  
    257     /// <summary>
    258     /// This method tries to change the state the FSM is in based on
    259     /// the current state and the transition passed. If current state
    260     ///  doesn't have a target state for the transition passed, 
    261     /// an ERROR message is printed.
    262     /// </summary>
    263     public void PerformTransition(Transition trans)
    264     {
    265         // Check for NullTransition before changing the current state
    266         if (trans == Transition.NullTransition)
    267         {
    268             Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
    269             return;
    270         }
    271  
    272         // Check if the currentState has the transition passed as argument
    273         StateID id = currentState.GetOutputState(trans);
    274         if (id == StateID.NullStateID)
    275         {
    276             Debug.LogError("FSM ERROR: State " + currentStateID.ToString() +  " does not have a target state " + 
    277                            " for transition " + trans.ToString());
    278             return;
    279         }
    280  
    281         // Update the currentStateID and currentState        
    282         currentStateID = id;
    283         foreach (FSMState state in states)
    284         {
    285             if (state.ID == currentStateID)
    286             {
    287                 // Do the post processing of the state before setting the new one
    288                 currentState.DoBeforeLeaving();
    289  
    290                 currentState = state;
    291  
    292                 // Reset the state to its desired condition before it can reason or act
    293                 currentState.DoBeforeEntering();
    294                 break;
    295             }
    296         }
    297  
    298     } // PerformTransition()
    299  
    300 } //class FSMSystem
    View Code

    例子:

    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;
     
        public void SetTransition(Transition t) { fsm.PerformTransition(t); }
     
        public void Start()
        {
            MakeFSM();
        }
     
        public void FixedUpdate()
        {
            fsm.CurrentState.Reason(player, gameObject);
            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);
            follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);
     
            ChasePlayerState chase = new ChasePlayerState();
            chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
     
            fsm = new FSMSystem();
            fsm.AddState(follow);
            fsm.AddState(chase);
        }
    }
     
    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);
            }
        }
     
        public override void Act(GameObject player, GameObject npc)
        {
            // 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;
        }
     
    } // ChasePlayerState
    View Code
  • 相关阅读:
    ZigBee学习二 LED点对点通信
    ZigBee学习一 任务处理函数_ProcessEvent
    关于count(分组字段)的问题
    hive命令行 显示字段名配置
    Linux 查看当前目录下的文件大小
    apache 端口号与 CDH端口号对比
    dbeaver驱动问题解决方案
    【数学】递推算法之平面分割问题总结
    【HDOJ】(1426)Sudoku Killer (dfs)
    【牛客】牛客小白月赛1(数学)
  • 原文地址:https://www.cnblogs.com/naix-x/p/4924078.html
Copyright © 2011-2022 走看看