有限状态机一般在unity的运用就是用来制作AI的,相比于行为树制作AI来说的话,有限状态机在制作AI方面会更加灵活,实现的效果会更加多元,但是也会相对行为树插件来说会更加麻烦
有点类似于动画状态机的效果,因此在学习FSM状态机的时候可以参考动画状态机
状态分三个生命周期,1.进入状态 2.执行中 3.结束状态(在FSMState通过三个虚方法来表示)
有限状态机FSM需要有以下几个固定脚本
Transition为状态机转换的条件--Framework
StateID为每个状态机的状态唯一标识码--Framework
FSMState为状态机(父类),其中有状态机的状态(用状态码来标识),还有转换状态条件(用字典存储)--Framework
FSMManager为状态机管理类,其中就用多个状态机,当前执行的状态机和添加删除切换当前执行的状态机等方法--Framework
有了框架部分的内容,后续就是为书写每个状态具体的脚本
Transition脚本
/// <summary>
/// 有哪些状态转换条件
/// </summary>
public enum Transition
{
NullTransition=0,
CanSeeObject,
CanotSeeObject,
}
StateID脚本
/// <summary>
/// 状态标识符,每个状态对应一个标识符
/// </summary>
public enum StateID
{
NullStateID=0,
Patrol,
Chase,
}
FSMState状态机脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 抽象状态类,把状态类的相同功能提取出来放在共同抽象父类
/// </summary>
public abstract class FSMState
{
protected StateID stateID;
public StateID StateID { get { return stateID; } }
//对于当前对象来说,不同转换条件下就会对应不同一种状态
protected Dictionary<Transition,StateID> stateTransDict = new Dictionary<Transition,StateID>();
public FSMManager fSMManager;//这里需要指定状态机所属的状态机管理器,方便在子类脚本调用切换状态
/// <summary>
/// 添加转换条件
/// </summary>
/// <param name="trans">转换条件</param>
/// <param name="id">转换的状态码</param>
public void AddTransition(Transition trans,StateID id)
{
if(trans==Transition.NullTransition||id==StateID.NullStateID)
{
Debug.LogError("Transition or state is null");
return;
}
if(stateTransDict.ContainsKey(trans) )
{
Debug.LogError($"The Transition: {trans} has existed");
return;
}
stateTransDict.Add(trans,id);
}
/// <summary>
/// 移除转换条件
/// </summary>
/// <param name="trans">要移除的转换条件</param>
public void RemoveTransition(Transition trans)
{
if(!stateTransDict.ContainsKey(trans))
{
Debug.LogError($"The Transition: {trans} is not existed");
return;
}
stateTransDict.Remove(trans);
}
/// <summary>
/// 根据传递过来的转换条件,判断是否能返回想要转换的状态
/// </summary>
/// <param name="trans">切换条件</param>
/// <returns></returns>
public StateID GetState(Transition trans)
{
if(stateTransDict.ContainsKey(trans))
{
return stateTransDict[trans];
}
return StateID.NullStateID;
}
/// <summary>
/// 在进入状态之前,需要做的事
/// 注意这里是虚方法,和下方的抽象方法不同,子类可以重写该方法(具体差别去看抽象方法和虚方法)
/// </summary>
public virtual void DoBeforeEntering()
{
}
/// <summary>
/// 在离开状态之前需要做的事
/// </summary>
public virtual void DoBeforeLeaving()
{
}
/// <summary>
/// 当状态机出于执行状态时执行的方法
/// 注意这里是抽象方法,子类中必须实现该方法
/// </summary>
public abstract void DoUpdate();
}
FSMManager状态机管理器脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 状态机管理类
/// </summary>
public class FSMManager
{
//当前状态机管理器下面有哪些状态
private Dictionary<StateID, FSMState> states;
//状态机当前处于什么状态
private FSMState currentState;
public FSMState CurrentState { get => currentState; }
public FSMManager()
{
states=new Dictionary<StateID, FSMState>();
}
//往状态机管理器里面添加状态
public void AddState(FSMState state)
{
if(state==null)
{
Debug.LogError($"{state} is NULL");
return;
}
if(states.ContainsKey(state.StateID))
{
Debug.LogError($"{state} is existed");
return;
}
states.Add(state.StateID, state);
state.fSMManager = this;
}
//删除状态机管理器中的状态
public void RemoveState(FSMState state)
{
if (state == null)
{
Debug.LogError($"{state} is NULL");
return;
}
if (!states.ContainsKey(state.StateID))
{
Debug.LogError($"{state} is not existed");
return;
}
states.Remove(state.StateID);
}
//从当前状态转换到目标状态(参数为转换的条件)
public void TransState(Transition trans)
{
if(trans==Transition.NullTransition)
{
Debug.LogError($"{trans} is null");
return;
}
StateID id= currentState.GetState(trans);//获取到要切换的状态机ID
if(id==StateID.NullStateID)
{
return;
}
else
{
currentState.DoBeforeLeaving();//切换之前执行的方法
currentState = states[id];//切换状态
currentState.DoBeforeEntering();//进入到新的状态机执行的方法
}
}
//启动状态机管理器时当前的默认状态
public void StartFSMManager(StateID id)
{
if (id == StateID.NullStateID)
{
Debug.LogError($"{id} is null");
return;
}
currentState = states[id];
currentState.DoBeforeLeaving();
currentState.DoBeforeEntering();
}
}
以下脚本就是针对不同的AI行为可以进行不同的脚本控制
控制NPC的脚本,该脚本就是用来控制NPC状态的切换等功能
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPCControl : MonoBehaviour
{
private FSMManager fSMManager;//每个角色都应该有个状态机管理器,参考动画状态机
public Transform[] wayPoints;//NPC巡逻路径点
public GameObject player;
// Start is called before the first frame update
void Start()
{
InitFSM();
}
void InitFSM()//初始化状态机
{
fSMManager = new FSMManager();
PatrolState patrolState = new PatrolState(wayPoints,this.gameObject,player);//创建NPC的状态
patrolState.AddTransition(Transition.CanSeeObject,StateID.Chase);//为巡逻状态机添加转换条件,在看见玩家的时候就切换为追逐状态
ChaseState chaseState = new ChaseState(this.gameObject, player);
chaseState.AddTransition(Transition.CanotSeeObject, StateID.Patrol);
fSMManager.AddState(patrolState);//为状态机管理器添加NPC的状态机
fSMManager.AddState(chaseState);
fSMManager.StartFSMManager(StateID.Patrol);
}
private void Update()
{
fSMManager.CurrentState.DoUpdate();//每一帧都会执行当前状态的方法
}
}
例如NPC追逐目标的AI脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChaseState : FSMState//追逐状态
{
private GameObject npc;
private Rigidbody npcRd;//npc的刚体
private GameObject player;
public ChaseState(GameObject npc, GameObject player)
{
stateID=StateID.Chase;
this.npc = npc;
npcRd = npc.GetComponent<Rigidbody>();
this.player = player;
}
public override void DoBeforeEntering()
{
Debug.Log("进入追逐状态机");
}
public override void DoUpdate()
{
npcRd.velocity= npc.transform.forward * 3;
Vector3 targetPosition=player.transform.position;
npc.transform.LookAt(targetPosition);
CheckTransition();
}
private void CheckTransition()//检查转换条件
{
if (Vector3.Distance(player.transform.position, npc.transform.position) > 10)
{
fSMManager.TransState(Transition.CanotSeeObject);
}
}
}
NPC巡逻脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolState : FSMState//巡逻状态
{
private int targetPointIndex;
private Transform[] wayPoints;
private GameObject npc;
private Rigidbody npcRd;//npc的刚体
private GameObject player;
public PatrolState(Transform[] wayPoints, GameObject npc,GameObject player)
{
stateID = StateID.Patrol;
this.wayPoints = wayPoints;
this.npc = npc;
npcRd=npc.GetComponent<Rigidbody>();
this.player = player;
}
public override void DoBeforeEntering()
{
Debug.Log("进入巡逻状态机");
}
public override void DoUpdate()
{
Patrol();
CheckTransition();
}
public override void DoBeforeLeaving()
{
npcRd.velocity = npc.transform.forward * 0;
}
private void CheckTransition()//检查转换条件
{
if(Vector3.Distance(player.transform.position,npc.transform.position)<5)
{
fSMManager.TransState(Transition.CanSeeObject);
}
}
private void Patrol()//巡逻
{
npcRd.velocity = npc.transform.forward * 3;
Transform targetTrans = wayPoints[targetPointIndex];
Vector3 targetPosition = targetTrans.position;
npc.transform.LookAt(targetPosition);
if (Vector3.Distance(npc.transform.position, targetPosition) < 0.1f)
{
targetPointIndex++;
targetPointIndex %= wayPoints.Length;
}
}
}