一个有限状态机是一个设备,或是一个设备模型。具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,是的从一个状态变换到另一个状态,或者是促使一个输出或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。
基于状态模式
有限状态机设计的核心原则就是:单一职责原则和里氏替换原则。单一职责就是每一个状态都有专门的一个脚本进行处理他的行为。里氏替换原则:所有具体状态类继承于一个抽象类,这样不管是那个状态实例化的对象,都可以借助基类进行。
状态机模式的实现需要三个要点:
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);
}
}