首页 > 代码库 > Unity FSM 有限状态机
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; 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 FSM 有限状态机
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。