一個有限狀态機是一個裝置,或是一個裝置模型。具有有限數量的狀态,它可以在任何給定的時間根據輸入進行操作,是的從一個狀态變換到另一個狀态,或者是促使一個輸出或者一種行為的發生。一個有限狀态機在任何瞬間隻能處在一種狀态。
基于狀态模式
有限狀态機設計的核心原則就是:單一職責原則和裡氏替換原則。單一職責就是每一個狀态都有專門的一個腳本進行處理他的行為。裡氏替換原則:所有具體狀态類繼承于一個抽象類,這樣不管是那個狀态執行個體化的對象,都可以借助基類進行。
狀态機模式的實作需要三個要點:
1、為所有的狀态定義一個接口或者基類别
2、為每個狀态定義一個類
3、恰當進行狀态的委托(關聯起來,怎麼實作類和方法的調用)
①狀态基類(FSMState)
在FSMState類中,有兩個枚舉,第一個枚舉Transition存放所有狀态轉換的條件,第二個人枚舉StateID存放的是所有的狀态,當我們增加和删除狀态的時候直接在這兩個枚舉中添加和删除就可以了。FSMState中有一個鍵值對map,這個鍵值對存放的是狀态的轉換條件和目标狀态,是用于在後面我們轉換狀态的時候進行使用,判斷是否存在這樣的轉換條件和狀态。FSMState類中有七個函數,第一個AddTransition是進行狀态轉換的添加,第二個DeleteTransition是進行狀态轉換的删除,第三個GetOutputState是擷取轉換條件相應的狀态,第四個DoBeforeEntering是目前狀态開始時的操作,第五個DoBeforeLeaving是目前狀态離開時的操作,第六個Act是目前狀态執行時的操作,最後一個Reason就是轉換狀态的操作了。在這些的基礎上我添加了一個構造函數,這個構造函數有兩個作用,一是友善我們後續對狀态的标記,二是能快速的擷取到FSMSystem的對象,友善操作。
②狀态管理機(FSMSystem)
一個狀态管理機主要的功能有這兩點
能夠添加和删除狀态集(FSMState)
能夠切換和擷取某個狀态集(FSMState)目前的狀态(State)
FSMSystem類中的函數有三個,第一個AddState是注冊我們的狀态,第二個DeleteState是删除狀态,第三個PerformTransition就是通過轉換條件進行狀态之間的切換了,同樣,在這個的基礎上,我添加了一個函數Update,這個函數的作用是把狀态的Act函數和Reason函數進行執行,因為每一個狀态都要一直執行這兩個函數,是以直接封裝起來,友善使用。
Unity中FSM有限狀态機系統的構成和功能的簡單實作:
Enemy的StateID狀态和Trasition條件轉換關系圖
/// <summary>
/// 條件轉換枚舉類型
/// </summary>
public enum Transition {
NullTransition=0,
SawPlayer,
LostPlayer,
}
/// <summary>
/// 為狀态加入枚舉标簽 枚舉狀态ID
/// </summary>
public enum StateID
{
NullStateID=0,
ChasingPlayer,
FolowingPath,
}
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 狀态基類接口
/// 這個類代表狀态在有限狀态機系統中
/// 每個狀态都有一個由一對搭檔(過渡-狀态)組成的字典來表示目前狀态下如果一個過渡被觸發狀态機會進入那個狀态
/// Reason方法被用來決定那個過渡會被觸發
/// Act方法來表現NPC出在目前狀态的行為
/// </summary>
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 transition,StateID id)
{
if (transition==Transition.NullTransition)
{
Debug.Log("transition不存在");
return;
}
if (id==StateID.NullStateID)
{
Debug.Log("NullStateID");
return;
}
if (map.ContainsKey(transition))
{
return;
}
map.Add(transition,id);
}
public void DeleteTransition(Transition transition)
{
if (transition==Transition.NullTransition)
{
return;
}
if (map.ContainsKey(transition))
{
map.Remove(transition);
return;
}
Debug.LogError("不存在transition");
}
/// <summary>
/// 該方法在該狀态接收到一個過渡時傳回狀态機需要成為的新狀态
/// </summary>
public StateID GetOutputState(Transition transition )
{
if (map.ContainsKey(transition))
{
return map[transition];
}
return StateID.NullStateID;
}
/// <summary>
/// 進來之前做的
/// </summary>
public virtual void DoBeforeEnter() { }
/// 這個方法用來讓一切都是必要的,例如在有限狀态機變化的另一個時重置變量。
/// 在狀态機切換到新的狀态之前它會被自動調用。
/// </summary>
public virtual void DoBeforeLeaving() { }
/// <summary>
/// 動機-->這個方法用來決定目前狀态是否需要過渡到清單中的其他狀态
/// NPC is a reference to the object that is controlled by this class
/// NPC是被該類限制下對象的一個引用
/// </summary>
public abstract void Reason(GameObject player,GameObject npc);
/// <summary>
/// This method controls the behavior of the NPC in the game World.
/// 表現-->該方法用來控制NPC在遊戲世界中的行為
/// Every action, movement or communication the NPC does should be placed here
/// NPC的任何動作,移動或者交流都需要防止在這兒
/// NPC is a reference to the object that is controlled by this class
/// NPC是被該類限制下對象的一個引用
/// </summary>
public abstract void Act(GameObject player, GameObject npc);
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 狀态機的擁有者類
/// 它持有者NPC的狀态集合并且有添加,删除狀态的方法,以及改變目前正在執行的狀态
/// </summary>
public class FSMSystem
{
private List<FSMState> states;
private StateID currentStateID;
public StateID CurrentStateID
{
get
{
return currentStateID;
}
}
private FSMState currentFsmState;
public FSMState CurrentFsmState
{
get
{
return currentFsmState;
}
}
public FSMSystem()
{
states=new List<FSMState>();
}
/// <summary>
/// 這個方法為有限狀态機置入新的狀态
/// 或者在該狀态已經存在于清單中時列印錯誤資訊
/// 第一個添加的狀态也是最初的狀态!
/// </summary>
public void AddState(FSMState s)
{
if (s==null)
{
Debug.LogError("FSM ERROR: Null reference is not allowed");
}
if (states.Count==0)
{
states.Add(s);
currentFsmState = s;
currentStateID = s.ID;
return;
}
foreach (FSMState state in states)
{
if (state.ID==s.ID)
{
return;
}
}
states.Add(s);
}
public void DeleteState(StateID id)
{
if (id==StateID.NullStateID)
{
return;
}
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");
}
public void PerformTransition(Transition transition)
{
if (transition==Transition.NullTransition)
{
return;
}
StateID id=currentFsmState.GetOutputState(transition);
if (id==StateID.NullStateID)
{
return;
}
currentStateID = id;
foreach (FSMState state in states)
{
if (state.ID==currentStateID)
{
currentFsmState.DoBeforeLeaving();
currentFsmState = state;
currentFsmState.DoBeforeEnter();
break;
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowPathState : FSMState
{
private int currentWayPoint;
private Transform[] wayPoints;
public FollowPathState(Transform[] wp)
{
this.wayPoints = wp;
currentWayPoint = 0;
stateId = StateID.FolowingPath;
}
public override void DoBeforeEnter()
{
}
public override void DoBeforeLeaving()
{
}
public override void Act(GameObject player, GameObject npc)
{
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;
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);
}
npc.GetComponent<Rigidbody>().velocity = vel;
}
public override void Reason(GameObject player, GameObject npc)
{
RaycastHit hit;
if (Physics.Raycast(npc.transform.position,npc.transform.forward,out hit,20))
{
if (hit.transform.gameObject.tag=="Player")
{
npc.GetComponent<NpcControll>().SteTransition(Transition.SawPlayer);
}
}
}
}
using UnityEngine;
public class ChasePlayerState : FSMState
{
//構造函數裝填自己
public ChasePlayerState()
{
stateId = StateID.ChasingPlayer;
}
public override void Reason(GameObject player, GameObject npc)
{
if (Vector3.Distance(npc.transform.position, player.transform.position) >= 3)
npc.GetComponent<NpcControll>().SteTransition(Transition.LostPlayer);
}
public override void Act(GameObject player, GameObject npc)
{
Vector3 vel = npc.GetComponent<Rigidbody>().velocity;
Vector3 moveDir = player.transform.position - npc.transform.position;
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;
npc.GetComponent<Rigidbody>().velocity = vel;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//[RequireComponent(typeof(Rigidbody))]
public class NpcControll : MonoBehaviour
{
public GameObject player;
public Transform[] paths;
private FSMSystem fsm;
public void SteTransition(Transition transition)
{
fsm.PerformTransition(transition);
}
void Start ()
{
MakeFSM();
}
void MakeFSM()
{
FollowPathState follow=new FollowPathState(paths);
follow.AddTransition(Transition.SawPlayer,StateID.ChasingPlayer);
ChasePlayerState chase=new ChasePlayerState();
chase.AddTransition(Transition.LostPlayer,StateID.FolowingPath);
fsm=new FSMSystem();
fsm.AddState(follow);
fsm.AddState(chase);
}
void FixedUpdate()
{
fsm.CurrentFsmState.Reason(player,gameObject);
fsm.CurrentFsmState.Act(player,gameObject);
}
}