
Unity 进阶 之 实现简单(AI)的状态机(FSM)管理Npc状态
目录
Unity 进阶 之 实现简单(AI)的状态机(FSM)管理Npc状态
一、简单介绍
二、实现原理
三、注意事项
四、效果预览
五、实现步骤
六、关键代码
一、简单介绍
Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。
本节介绍,使用状态机,进行管理 Npc 状态切换管理,如果你有更好的方法,欢迎留言交流。
有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。任何一个FSM都可以用状态转换图来描述,图中的节点表示FSM中的一个状态,有向加权边表示输入字符时状态的变化。如果图中不存在与当前状态与输入字符对应的有向边,则FSM将进入“消亡状态(Doom State)”,此后FSM将一直保持“消亡状态”。状态转换图中还有两个特殊状态:状态1称为“起始状态”,表示FSM的初始状态。状态6称为“结束状态”,表示成功识别了所输入的字符序列。
在启动一个FSM时,首先必须将FSM置于“起始状态”,然后输入一系列字符,最终,FSM会到达“结束状态”或者“消亡状态”。

说明:
在通常的FSM模型中,一般还存在一个“接受状态”,并且FSM可以从“接受状态”转换到另一个状态,只有在识别最后一个字符后,才会根据最终状态来决定是否接受所输入的字符串。此外,也可以将“其实状态”也作为接受状态,因此空的输入序列也是可以接受的。
二、实现原理
1、BaseState 设置状态管理的基本状态,实现状态过渡的添加和删除,以及进入退出该状态的回调函数,再到该状态的执行以及切换状态的监控;
// 状态ID
protected StateID mStateID;
// 状态装换 对应的 状态ID 字典集合
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
// 有限状态机(方便 Reason() 函数满足条件后状态切换)
protected FiniteStateMachine mFiniteStateMachine;
public void AddTransition(Transition trans, StateID id){}
public void DeleteTransition(Transition trans){}
// 状态切换进入前的回调
public virtual void DoBeforeEntering() { }
// 状态切换退出的状态的回调
public virtual void DoAfterLeaving() { }
public abstract void Act(GameObject npc); // 执行的回调
public abstract void Reason(GameObject npc); // 转换条件
2、FiniteStateMachine 管理各个?BaseState 的添加删除,以及根据过渡条件切换状态
private Dictionary<StateID, BaseState> statesDict = new Dictionary<StateID, BaseState>();
private StateID mCurrentStateId;
private BaseState mCurrentFSMState;
public void AddState(BaseState fsmState){}
public void DeleteState(StateID stateID){}
public void PerformTransition(Transition trans) {}
public void DoUpdate(GameObject npc){}
3、StateID 状态 ID ,Transition 管理各个状态的切换过渡
/// <summary>
/// 状态 ID
/// 可以根据需要添加多种 ID
/// </summary>
public enum StateID
{
None=0,
// NPC
Patrol,
Chase,
// UI
}
/// <summary>
/// 过渡 ID
/// 可以根据需要添加多种 过渡 ID
/// </summary>
public enum Transition
{
None,
// NPC
SeePlayer,
LostPlayer,
// UI
}

三、注意事项
1、根据需要继承 BaseState ,实现对应的抽象函数,根据需要实现虚函数
2、这里是简单的有限状态机,需要额外的功能,可以在此基础上添加或者重写
3、这里只添加了 NPC 的的巡逻和追击状态管理,可根据其他添加其他的状态管理
四、效果预览

五、实现步骤
1、打开 Unity,布置场景,添加 NPC ,和路径点,添加一个简单 Player

2、在工程中添加脚本,实现有限状态机的管理,以及 NPC 的具体状态管理

3、把 NPC 脚本添加到 NPC?

4、运行场景,NPC 自动寻路巡逻,遇到 Player 追击,Player 跑开,则继续巡逻

六、关键代码
1、BaseState
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XANFSM {
/// <summary>
/// 基础状态
/// </summary>
public abstract class BaseState
{
// 状态ID
protected StateID mStateID;
public StateID MStateID { get { return mStateID; } }
// 状态装换 对应的 状态ID 字典集合
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
// 有限状态机(方便 Reason() 函数满足条件后状态切换)
protected FiniteStateMachine mFiniteStateMachine;
// 构造函数
public BaseState(FiniteStateMachine finiteStateMachine) {
mFiniteStateMachine = finiteStateMachine;
}
/// <summary>
/// 添加状态过渡对应的状态到字典中
/// </summary>
/// <param name="trans">状态过渡</param>
/// <param name="id">状态ID</param>
public void AddTransition(Transition trans, StateID id)
{
if (trans == Transition.None)
{
Debug.LogError(GetType()+ "/AddTransition()/ transition is None");
return;
}
if (id == StateID.None)
{
Debug.LogError(GetType() + "/AddTransition()/ stateId is None");
return;
}
if (map.ContainsKey(trans))
{
Debug.LogError(GetType() + "/AddTransition()/ transition map has already existed, transition = "+trans);
return;
}
map.Add(trans,id);
}
// 删除状态集合中的指定的状态过渡
public void DeleteTransition(Transition trans)
{
if (trans == Transition.None)
{
Debug.LogError(GetType() + "/DeleteTransition()/ transition is None");
return;
}
if (map.ContainsKey(trans)==false)
{
Debug.LogError(GetType() + "/DeleteTransition()/ transition map has not existed, transition = " + trans);
return;
}
map.Remove(trans);
}
/// <summary>
/// 获取对应状态过渡对应的状态
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
public StateID GetStateWithTransition(Transition trans) {
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.None;
}
// 状态切换进入前的回调
public virtual void DoBeforeEntering() { }
// 状态切换退出的状态的回调
public virtual void DoAfterLeaving() { }
public abstract void Act(GameObject npc); // 执行的回调
public abstract void Reason(GameObject npc); // 转换条件
}
}
2、FiniteStateMachine?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XANFSM {
/// <summary>
/// 有限状态机
/// </summary>
public class FiniteStateMachine
{
private Dictionary<StateID, BaseState> statesDict = new Dictionary<StateID, BaseState>();
private StateID mCurrentStateId;
private BaseState mCurrentFSMState;
/// <summary>
/// Update 更新
/// </summary>
/// <param name="npc"> GameObject 可以根据需要进行更改 Object </param>
public void DoUpdate(GameObject npc) {
mCurrentFSMState.Act(npc);
mCurrentFSMState.Reason(npc);
}
/// <summary>
/// 添加状态到字典中
/// </summary>
/// <param name="fsmState"></param>
public void AddState(BaseState fsmState) {
if (fsmState==null)
{
Debug.LogError(GetType() + "/AddState()/ fsmState is null");
return;
}
if (statesDict.ContainsKey(fsmState.MStateID))
{
Debug.LogError(GetType() + "/AddState()/ statesDict had already existed, fsmState.StateID = "+ fsmState.MStateID);
return;
}
// 设置默认状态
if (mCurrentFSMState == null)
{
mCurrentFSMState = fsmState;
mCurrentStateId = fsmState.MStateID;
}
statesDict.Add(fsmState.MStateID,fsmState);
}
/// <summary>
/// 删除状态
/// </summary>
/// <param name="stateID"></param>
public void DeleteState(StateID stateID)
{
if (stateID == StateID.None)
{
Debug.LogError(GetType() + "/DeleteState()/ stateID is None");
return;
}
if (statesDict.ContainsKey(stateID)==false)
{
Debug.LogError(GetType() + "/DeleteState()/ statesDict had not existed, StateID = " + stateID);
return;
}
statesDict.Remove(stateID);
}
/// <summary>
/// 状态切换到指定状态
/// </summary>
/// <param name="trans"></param>
public void PerformTransition(Transition trans) {
if (trans == Transition.None)
{
Debug.LogError(GetType() + "/PerformTransition()/ trans is None,空转换条件无法执行" );
return;
}
StateID stateID = mCurrentFSMState.GetStateWithTransition(trans);
if (stateID==StateID.None)
{
Debug.LogWarning(GetType() + "/PerformTransition()/ stateID form GetOutputState(trans) is None,状态为空");
return;
}
if (statesDict.ContainsKey(stateID) ==false)
{
Debug.LogError(GetType() + "/PerformTransition()/ statesDict had not stateID,StateID = " +stateID);
return;
}
// 退出当前状态的前的回调
if (mCurrentFSMState!=null)
{
mCurrentFSMState.DoAfterLeaving();
}
// 状态更新
BaseState state = statesDict[stateID];
mCurrentFSMState = state;
mCurrentStateId = stateID;
// 切换后新当前状态的前的回调
if (mCurrentFSMState != null)
{
mCurrentFSMState.DoBeforeEntering();
}
}
}
}
3、ChaseState
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XANFSM.Test {
/// <summary>
/// 追赶状态
/// </summary>
public class ChaseState : BaseState
{
Transform mPlayerTrans;
public ChaseState(FiniteStateMachine finiteStateMachine) : base(finiteStateMachine)
{
mStateID = StateID.Chase; // 设置状态 ID
mPlayerTrans = GameObject.Find("Player").transform;
}
/// <summary>
/// 状态中执行函数
/// </summary>
/// <param name="npc"></param>
public override void Act(GameObject npc)
{
npc.transform.LookAt(mPlayerTrans.position);
npc.transform.Translate(Vector3.forward * 2 * Time.deltaTime);
}
/// <summary>
/// 切换条件监听
/// </summary>
/// <param name="npc"></param>
public override void Reason(GameObject npc)
{
if (Vector3.Distance(npc.transform.position, mPlayerTrans.position) > 6)
{
mFiniteStateMachine.PerformTransition(Transition.LostPlayer);
}
}
}
}
4、PatrolState?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XANFSM.Test {
/// <summary>
/// 巡逻状态
/// </summary>
public class PatrolState : BaseState
{
List<Transform> mPath =new List<Transform>();
int mIndex = 0;
Transform mPlayerTrans;
// 构造函数
public PatrolState(FiniteStateMachine fsmStateManager) : base(fsmStateManager)
{
mStateID = StateID.Patrol;
Transform pathTrans = GameObject.Find("Path").transform;
Transform[] children = pathTrans.GetComponentsInChildren<Transform>();
foreach (Transform child in children)
{
if (child!= pathTrans)
{
mPath.Add(child);
}
}
mPlayerTrans = GameObject.Find("Player").transform;
}
// 巡逻函数
public override void Act(GameObject npc)
{
npc.transform.LookAt(mPath[mIndex].position);
npc.transform.Translate(Vector3.forward * Time.deltaTime *3);
if (Vector3.Distance(npc.transform.position, mPath[mIndex].position)<1)
{
mIndex++;
mIndex %= mPath.Count;
}
}
// 状态监听切换函数
public override void Reason(GameObject npc)
{
if (Vector3.Distance(npc.transform.position , mPlayerTrans.position)<3)
{
mFiniteStateMachine.PerformTransition(Transition.SeePlayer);
}
}
}
}
5、NPC
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XANFSM.Test {
public class NPC : MonoBehaviour
{
private FiniteStateMachine mFSMStateManager; // NPC 的状态机
// Start is called before the first frame update
void Start()
{
InitFSM();
}
// Update is called once per frame
void Update()
{
// 状态机运行
mFSMStateManager.DoUpdate(this.gameObject);
}
/// <summary>
/// 绑定 NPC 状态机
/// </summary>
void InitFSM() {
mFSMStateManager = new FiniteStateMachine(); // 构建
// 巡逻状态
PatrolState patrolState = new PatrolState(mFSMStateManager);
patrolState.AddTransition(Transition.SeePlayer,StateID.Chase);
// 追赶状态
ChaseState chaseState = new ChaseState(mFSMStateManager);
chaseState.AddTransition(Transition.LostPlayer,StateID.Patrol);
// 添加状态到状态击中
mFSMStateManager.AddState(patrolState);
mFSMStateManager.AddState(chaseState);
}
}
}

|