Unity FSM 有限狀態機


翻譯了一下unity wiki上對於有限狀態機的案例,等有空時在詳細寫一下。在場景中添加兩個游戲物體,一個為玩家並修改其Tag為Player,另一個為NPC為其添加NPCControl腳本,並為其將玩家角色和路徑添加上去。(該案例利用狀態機簡單的實現了一個NPC的簡單AI---巡邏---看到玩家----追逐玩家----丟失玩家----巡邏

效果:

 

狀態機:

  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 /// 為過渡加入枚舉標簽
 52 /// 不要修改第一個標簽,NullTransition會在FSMSytem類中使用
 53 /// </summary>
 54 public enum Transition
 55 {
 56     NullTransition = 0, // Use this transition to represent a non-existing transition in your system
 57     //用這個過度來代表你的系統中不存在的狀態
 58     SawPlayer,//這里配合NPCControl添加兩個NPC的過渡
 59     LostPlayer,
 60 }
 61 
 62 /// <summary>
 63 /// Place the labels for the States in this enum.
 64 /// Don't change the first label, NullStateID as FSMSystem class uses it.
 65 /// 為狀態加入枚舉標簽
 66 /// 不要修改第一個標簽,NullStateID會在FSMSytem中使用 
 67 /// </summary>
 68 public enum StateID
 69 {
 70     NullStateID = 0, // Use this ID to represent a non-existing State in your syste
 71     //使用這個ID來代表你系統中不存在的狀態ID    
 72     ChasingPlayer,//這里配合NPCControl添加兩個狀態
 73     FollowingPath,
 74 
 75 }
 76 
 77 /// <summary>
 78 /// This class represents the States in the Finite State System.
 79 /// Each state has a Dictionary with pairs (transition-state) showing
 80 /// which state the FSM should be if a transition is fired while this state
 81 /// is the current state.
 82 /// Method Reason is used to determine which transition should be fired .
 83 /// Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
 84 /// 這個類代表狀態在有限狀態機系統中
 85 /// 每個狀態都有一個由一對搭檔(過渡-狀態)組成的字典來表示當前狀態下如果一個過渡被觸發狀態機會進入那個狀態
 86 /// Reason方法被用來決定那個過渡會被觸發
 87 /// Act方法來表現NPC出在當前狀態的行為
 88 /// </summary>
 89 public abstract class FSMState
 90 {
 91     protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
 92     protected StateID stateID;
 93     public StateID ID { get { return stateID; } }
 94 
 95     public void AddTransition(Transition trans, StateID id)
 96     {
 97         // Check if anyone of the args is invalid
 98         //驗證每個參數是否合法
 99         if (trans == Transition.NullTransition)
100         {
101             Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
102             return;
103         }
104 
105         if (id == StateID.NullStateID)
106         {
107             Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
108             return;
109         }
110 
111         // Since this is a Deterministic FSM,
112         //   check if the current transition was already inside the map
113         //要知道這是一個確定的有限狀態機(每個狀態后金對應一種狀態,而不能產生分支)
114         //檢查當前的過渡是否已經在地圖字典中了
115         if (map.ContainsKey(trans))
116         {
117             Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
118                            "Impossible to assign to another state");
119             return;
120         }
121 
122         map.Add(trans, id);
123     }
124 
125     /// <summary>
126     /// This method deletes a pair transition-state from this state's map.
127     /// If the transition was not inside the state's map, an ERROR message is printed.
128     /// 這個方法用來在狀態地圖中刪除transition-state對兒
129     /// 如果過渡並不存在於狀態地圖中,那么將會打印出一個錯誤
130     /// </summary>
131     public void DeleteTransition(Transition trans)
132     {
133         // Check for NullTransition
134         if (trans == Transition.NullTransition)
135         {
136             Debug.LogError("FSMState ERROR: NullTransition is not allowed");
137             return;
138         }
139 
140         // Check if the pair is inside the map before deleting
141         //再刪除之前確認該鍵值對是否存在於狀態地圖中(鍵值對集合)
142         if (map.ContainsKey(trans))
143         {
144             map.Remove(trans);
145             return;
146         }
147         Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
148                        " was not on the state's transition list");
149     }
150 
151     /// <summary>
152     /// This method returns the new state the FSM should be if
153     ///    this state receives a transition and 
154     /// 該方法在該狀態接收到一個過渡時返回狀態機需要成為的新狀態
155     /// </summary>
156     public StateID GetOutputState(Transition trans)
157     {
158         // Check if the map has this transition
159         if (map.ContainsKey(trans))
160         {
161             return map[trans];
162         }
163         return StateID.NullStateID;
164     }
165 
166     /// <summary>
167     /// This method is used to set up the State condition before entering it.
168     /// It is called automatically by the FSMSystem class before assigning it
169     /// to the current state.
170     /// 這個方法用來設立進入狀態前的條件
171     /// 在狀態機分配它到當前狀態之前他會被自動調用
172     /// </summary>
173     public virtual void DoBeforeEntering() { }
174 
175     /// <summary>
176     /// This method is used to make anything necessary, as reseting variables
177     /// before the FSMSystem changes to another one. It is called automatically
178     /// by the FSMSystem before changing to a new state.
179     /// 這個方法用來讓一切都是必要的,例如在有限狀態機變化的另一個時重置變量。
180     /// 在狀態機切換到新的狀態之前它會被自動調用。
181     /// </summary>
182     public virtual void DoBeforeLeaving() { }
183 
184     /// <summary>
185     /// This method decides if the state should transition to another on its list
186     /// 動機-->這個方法用來決定當前狀態是否需要過渡到列表中的其他狀態
187     /// NPC is a reference to the object that is controlled by this class
188     /// NPC是被該類約束下對象的一個引用
189     /// </summary>
190     public abstract void Reason(GameObject player, GameObject npc);
191 
192     /// <summary>
193     /// This method controls the behavior of the NPC in the game World.
194     /// 表現-->該方法用來控制NPC在游戲世界中的行為
195     /// Every action, movement or communication the NPC does should be placed here
196     /// NPC的任何動作,移動或者交流都需要防止在這兒
197     /// NPC is a reference to the object that is controlled by this class
198     /// NPC是被該類約束下對象的一個引用
199     /// </summary>
200     public abstract void Act(GameObject player, GameObject npc);
201 
202 } // class FSMState
203 
204 
205 /// <summary>
206 /// FSMSystem class represents the Finite State Machine class.
207 ///  It has a List with the States the NPC has and methods to add,
208 ///  delete a state, and to change the current state the Machine is on.
209 /// 該類便是有限狀態機類
210 /// 它持有者NPC的狀態集合並且有添加,刪除狀態的方法,以及改變當前正在執行的狀態
211 /// </summary>
212 public class FSMSystem
213 {
214     private List<FSMState> states;
215 
216     // The only way one can change the state of the FSM is by performing a transition
217     // Don't change the CurrentState directly
218     //通過預裝一個過渡的唯一方式來蓋面狀態機的狀態
219     //不要直接改變當前的狀態
220     private StateID currentStateID;
221     public StateID CurrentStateID { get { return currentStateID; } }
222     private FSMState currentState;
223     public FSMState CurrentState { get { return currentState; } }
224 
225     public FSMSystem()
226     {
227         states = new List<FSMState>();
228     }
229     /// <summary>
230     /// This method places new states inside the FSM,
231     /// or prints an ERROR message if the state was already inside the List.
232     /// First state added is also the initial state.
233     /// 這個方法為有限狀態機置入新的狀態
234     /// 或者在該狀態已經存在於列表中時打印錯誤信息
235     /// 第一個添加的狀態也是最初的狀態!
236     /// </summary>
237     public void AddState(FSMState s)
238     {
239         // Check for Null reference before deleting
240         //在添加前檢測空引用
241         if (s == null)
242         {
243             Debug.LogError("FSM ERROR: Null reference is not allowed");
244         }
245 
246        
247 
248         // First State inserted is also the Initial state,
249         //   the state the machine is in when the   begins
250         //被裝在的第一個狀態也是初始狀態
251         //這個狀態便是狀態機開始時的狀態
252         if (states.Count == 0)
253         {
254             states.Add(s);
255             currentState = s;
256             currentStateID = s.ID;
257             return;
258         }
259 
260         // Add the state to the List if it's not inside it
261         //如果該狀態未被添加過,則加入集合
262         foreach (FSMState state in states)
263         {
264             if (state.ID == s.ID)
265             {
266                 Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
267                                " because state has already been added");
268                 return;
269             }
270         }
271         states.Add(s);
272     }
273 
274     /// <summary>
275     /// This method delete a state from the FSM List if it exists, 
276     ///   or prints an ERROR message if the state was not on the List.
277     /// 該方法刪除一個已存在以狀態幾個中的狀態
278     /// 在它不存在時打印錯誤信息
279     /// </summary>
280     public void DeleteState(StateID id)
281     {
282         // Check for NullState before deleting
283         //在刪除前檢查其是否為空狀態
284         if (id == StateID.NullStateID)
285         {
286             Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
287             return;
288         }
289 
290         // Search the List and delete the state if it's inside it
291         //遍歷集合如果存在該狀態則刪除它
292         foreach (FSMState state in states)
293         {
294             if (state.ID == id)
295             {
296                 states.Remove(state);
297                 return;
298             }
299         }
300         Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
301                        ". It was not on the list of states");
302     }
303 
304     /// <summary>
305     /// This method tries to change the state the FSM is in based on
306     /// the current state and the transition passed. If current state
307     ///  doesn't have a target state for the transition passed, 
308     /// an ERROR message is printed.
309     /// 該方法基於當前狀態和過渡是否通過來嘗試改變狀態機的狀態,當當前的狀態沒有目標狀態用來過渡(叫通道應該更合適吧)時通過時則打印錯誤消息
310     /// </summary>
311     public void PerformTransition(Transition trans)
312     {
313         // Check for NullTransition before changing the current state
314         //在改變當前狀態前檢測NullTransition
315         if (trans == Transition.NullTransition)
316         {
317             Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
318             return;
319         }
320 
321         // Check if the currentState has the transition passed as argument
322         //在改變當前狀態前檢測當前狀態是否可作為過渡的參數
323 
324         StateID id = currentState.GetOutputState(trans);
325         if (id == StateID.NullStateID)
326         {
327             Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
328                            " for transition " + trans.ToString());
329             return;
330         }
331 
332         // Update the currentStateID and currentState        
333         //更新當前的狀態個和狀態編號
334         currentStateID = id;
335         foreach (FSMState state in states)
336         {
337             if (state.ID == currentStateID)
338             {
339                 // Do the post processing of the state before setting the new one
340                 //在狀態變為新狀態前執行后處理
341                 currentState.DoBeforeLeaving();
342 
343                 currentState = state;
344 
345                 // Reset the state to its desired condition before it can reason or act
346                 //在狀態可以使用Reason(動機)或者Act(行為)之前為它的的決定條件重置它自己
347                 currentState.DoBeforeEntering();
348                 break;
349             }
350         }
351 
352     } // PerformTransition()
353 
354 } //class FSMSystem

NPCControl:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using UnityEngine;
  5 
  6 [RequireComponent(typeof(Rigidbody))]
  7 public class NPCControl : MonoBehaviour
  8 {
  9     public GameObject player;
 10     public Transform[] path;
 11     private FSMSystem fsm;
 12 
 13     public void SetTransition(Transition t)
 14     {
 15         //該方法用來改變有限狀態機的狀體,有限狀態機基於當前的狀態和通過的過渡狀態。
 16         //如果當前的狀態沒有用來通過的過度狀態,則會拋出錯誤
 17         fsm.PerformTransition(t);
 18     }
 19 
 20     public void Start()
 21     {
 22         MakeFSM();
 23     }
 24 
 25     public void FixedUpdate()
 26     {
 27         fsm.CurrentState.Reason(player, gameObject);
 28         fsm.CurrentState.Act(player, gameObject);
 29     }
 30 
 31     
 32     //NPC有兩個狀態分別是在路徑中巡邏和追逐玩家
 33     //如果他在第一個狀態並且SawPlayer 過度狀態被出發了,它就轉變到ChasePlayer狀態
 34     //如果他在ChasePlayer狀態並且LostPlayer狀態被觸發了,它就轉變到FollowPath狀態
 35 
 36     private void MakeFSM()//建造狀態機
 37     {
 38         FollowPathState follow = new FollowPathState(path);
 39         follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);
 40 
 41         ChasePlayerState chase = new ChasePlayerState();
 42         chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
 43 
 44         fsm = new FSMSystem();
 45         fsm.AddState(follow);//添加狀態到狀態機,第一個添加的狀態將作為初始狀態
 46         fsm.AddState(chase);
 47     }
 48 }
 49 
 50 public class FollowPathState : FSMState
 51 {
 52     private int currentWayPoint;
 53     private Transform[] waypoints;
 54 
 55     //構造函數裝填自己
 56     public FollowPathState(Transform[] wp)
 57     {
 58         waypoints = wp;
 59         currentWayPoint = 0;
 60         stateID = StateID.FollowingPath;//別忘設置自己的StateID  61     }
 62 
 63     public override void DoBeforeEntering()
 64     {
 65         Debug.Log("FollowingPath BeforeEntering--------");
 66     }
 67 
 68     public override void DoBeforeLeaving()
 69     {
 70         Debug.Log("FollowingPath BeforeLeaving---------");
 71     }
 72 
 73     //重寫動機方法
 74     public override void Reason(GameObject player, GameObject npc)
 75     {
 76         // If the Player passes less than 15 meters away in front of the NPC
 77         RaycastHit hit;
 78         if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F))
 79         {
 80             if (hit.transform.gameObject.tag == "Player")
 81                 npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
 82         }
 83     }
 84 
 85     //重寫表現方法
 86     public override void Act(GameObject player, GameObject npc)
 87     {
 88         // Follow the path of waypoints
 89         // Find the direction of the current way point 
 90         Vector3 vel = npc.GetComponent<Rigidbody>().velocity;
 91         Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
 92 
 93         if (moveDir.magnitude < 1)
 94         {
 95             currentWayPoint++;
 96             if (currentWayPoint >= waypoints.Length)
 97             {
 98                 currentWayPoint = 0;
 99             }
100         }
101         else
102         {
103             vel = moveDir.normalized * 10;
104 
105             // Rotate towards the waypoint
106             npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
107                                                       Quaternion.LookRotation(moveDir),
108                                                       5 * Time.deltaTime);
109             npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
110 
111         }
112 
113         // Apply the Velocity
114         npc.GetComponent<Rigidbody>().velocity = vel;
115     }
116 
117 } // FollowPathState
118 
119 public class ChasePlayerState : FSMState
120 {
121     //構造函數裝填自己
122     public ChasePlayerState()
123     {
124         stateID = StateID.ChasingPlayer;
125     }
126 
127     public override void DoBeforeEntering()
128     {
129         Debug.Log("ChasingPlayer BeforeEntering--------");
130     }
131 
132     public override void DoBeforeLeaving()
133     {
134         Debug.Log("ChasingPlayer BeforeLeaving---------");
135     }
136 
137     public override void Reason(GameObject player, GameObject npc)
138     {
139         // If the player has gone 30 meters away from the NPC, fire LostPlayer transition
140         if (Vector3.Distance(npc.transform.position, player.transform.position) >= 3)
141             npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
142     }
143 
144     public override void Act(GameObject player, GameObject npc)
145     {
146         // Follow the path of waypoints
147         // Find the direction of the player         
148         Vector3 vel = npc.GetComponent<Rigidbody>().velocity;
149         Vector3 moveDir = player.transform.position - npc.transform.position;
150 
151         // Rotate towards the waypoint
152         npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation,
153                                                   Quaternion.LookRotation(moveDir),
154                                                   5 * Time.deltaTime);
155         npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);
156 
157         vel = moveDir.normalized * 10;
158 
159         // Apply the new Velocity
160         npc.GetComponent<Rigidbody>().velocity = vel;
161     }
162 
163 } // ChasePlayerState

 

Unity最受歡迎的插件,可以讓您的游戲如虎添翼,為您節省大量時間可以投入在游戲的創意和細節上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM