当我们在项目中有需要根据周围状况而改变自身状态的需求时,我们就可以使用状态机来实现。下面是我在WIKI上搜索的状态机示例程序。
首先是状态机状态以及管理脚本。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; //这里是切换状态变量 public enum Transition { NullTransition = 0, // Use this transition to represent a non-existing transition in your system SawPlayer, LostPlayer } //这个是每个状态的ID标识 public enum StateID { NullStateID = 0, // Use this ID to represent a non-existing State in your system ChasingPlayer, FollowingPath } 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) { // Check if anyone of the args is invalid if (trans == Transition.NullTransition) { Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition"); return; } if (id == StateID.NullStateID) { Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID"); return; } // Since this is a Deterministic FSM, // check if the current transition was already inside the map if (map.ContainsKey(trans)) { Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() + "Impossible to assign to another state"); return; } map.Add(trans, id); } public void DeleteTransition(Transition trans) { // Check for NullTransition if (trans == Transition.NullTransition) { Debug.LogError("FSMState ERROR: NullTransition is not allowed"); return; } // Check if the pair is inside the map before deleting 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) { // Check if the map has this transition if (map.ContainsKey(trans)) { return map[trans]; } return StateID.NullStateID; } /// <summary> /// This method is used to set up the State condition before entering it. /// It is called automatically by the FSMSystem class before assigning it /// to the current state. /// </summary> public virtual void DoBeforeEntering() { } /// <summary> /// This method is used to make anything necessary, as reseting variables /// before the FSMSystem changes to another one. It is called automatically /// by the FSMSystem before changing to a new state. /// </summary> public virtual void DoBeforeLeaving() { } /// <summary> /// This method decides if the state should transition to another on its list /// NPC is a reference to the object that is controlled by this class /// </summary> public abstract void Reason(GameObject player, GameObject npc);//一般用于检测状态是否需要改变 详情下面NPC代码 /// <summary> /// This method controls the behavior of the NPC in the game World. /// Every action, movement or communication the NPC does should be placed here /// NPC is a reference to the object that is controlled by this class /// </summary> public abstract void Act(GameObject player, GameObject npc);//执行该状态时要做的事情 详情下面NPC代码 } // class FSMState /// <summary> /// FSMSystem class represents the Finite State Machine class. /// It has a List with the States the NPC has and methods to add, /// delete a state, and to change the current state the Machine is on. /// </summary> public class FSMSystem { private List<FSMState> states; // The only way one can change the state of the FSM is by performing a transition // Don't change the CurrentState directly private StateID currentStateID; public StateID CurrentStateID { get { return currentStateID; } } private FSMState currentState; public FSMState CurrentState { get { return currentState; } } public FSMSystem() { states = new List<FSMState>(); } /// <summary> /// This method places new states inside the FSM, /// or prints an ERROR message if the state was already inside the List. /// First state added is also the initial state. /// </summary> public void AddState(FSMState s) { // Check for Null reference before deleting if (s == null) { Debug.LogError("FSM ERROR: Null reference is not allowed"); } // First State inserted is also the Initial state, // the state the machine is in when the simulation begins if (states.Count == 0) { states.Add(s); currentState = s; currentStateID = s.ID; return; } // Add the state to the List if it's not inside it 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); } /// <summary> /// This method delete a state from the FSM List if it exists, /// or prints an ERROR message if the state was not on the List. /// </summary> public void DeleteState(StateID id) { // Check for NullState before deleting if (id == StateID.NullStateID) { Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state"); return; } // Search the List and delete the state if it's inside it 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"); } /// <summary> /// This method tries to change the state the FSM is in based on /// the current state and the transition passed. If current state /// doesn't have a target state for the transition passed, /// an ERROR message is printed. /// </summary> public void PerformTransition(Transition trans) { // Check for NullTransition before changing the current state if (trans == Transition.NullTransition) { Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition"); return; } // Check if the currentState has the transition passed as argument StateID id = currentState.GetOutputState(trans); if (id == StateID.NullStateID) { Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " + " for transition " + trans.ToString()); return; } // Update the currentStateID and currentState currentStateID = id; foreach (FSMState state in states) { if (state.ID == currentStateID) { // Do the post processing of the state before setting the new one currentState.DoBeforeLeaving(); currentState = state; // Reset the state to its desired condition before it can reason or act currentState.DoBeforeEntering(); break; } } } }
然后创建一个NPC代码实现类来实现我们的NPC控制。
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.GetComponent<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.GetComponent<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); } //转到追逐状态 npc开始追寻Player public override void Act(GameObject player, GameObject npc) { // Follow the path of waypoints // Find the direction of the player Vector3 vel = npc.GetComponent<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.GetComponent<Rigidbody>().velocity = vel; } }