首页 > 编程语言 >【最通俗易懂】C#有限状态机

【最通俗易懂】C#有限状态机

时间:2022-11-29 20:31:19浏览次数:39  
标签:状态 StateID C# FSM 通俗易懂 状态机 state npc public

有限状态机

表示有限个状态以及在这些状态之间的转换和动作等行为的数学模型。

 

有限状态机框架:

  • Transition enum:此枚举包含可由系统触发的转换标签。
  • StateID枚举:这是游戏具有的状态ID。
  • FSMState类:有限状态机系统中的状态。每个状态都有一个字典,字典中有(转换-状态)键值对,保存Transition转换枚举类型的Key时,把枚举类型转换为int类型。(Enum没有实现IEquatable接口。因此,当我们使用Enum类型作为key值时,Dictionary的内部操作就需要将Enum类型转换为System.Object,这就导致了Boxing的产生。)
  • FSMSystem:这是有限状态机类,游戏中的每个NPC或GameObject必须具有这些类才能使用该框架。它将NPC的状态存储在List中,具有添加和删除状态的方法以及基于传递给它的转换来更改当前状态的方法PerformTransition()。您可以在代码中的任何位置调用此方法,如在碰撞测试中,或在Update()或FixedUpdate()中。

FSMSystem.cs

using System;
using System.Collections.Generic;
using UnityEngine;


/// <summary>
/// 转换状态
/// </summary>
public enum Transition
{
NullTransition = 0,
LostPlayer,
SawPlayer,
}

/// <summary>
/// 状态ID
/// </summary>
public enum StateID
{
NullStateID = 0,
FollowingPath,
ChasingPlayer,
}

/// <summary>
/// 有限状态机系统中的状态
/// 每个状态都有一个字典,字典中有键值对(转换-状态),保存转换Key时,把枚举转换为int,作为key
/// 表示如果在当前状态下触发转换,那么FSM应该处于对应的状态。
/// </summary>
public abstract class FSMState
{
protected Dictionary<int, StateID> m_Map = new Dictionary<int, StateID>();
protected StateID stateID;
public StateID ID { get { return stateID; } }

/// <summary>
/// 添加转换
/// </summary>
public void AddTransition(Transition trans, StateID id)
{
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;
}
int transition = (int)trans;
if (m_Map.ContainsKey(transition))
{
Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
"Impossible to assign to another state");
return;
}

m_Map.Add(transition, id);
}

/// <summary>
/// 删除转换
/// </summary>
public void DeleteTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
Debug.LogError("FSMState ERROR: NullTransition is not allowed");
return;
}
int transition = (int)trans;
if (m_Map.ContainsKey(transition))
{
m_Map.Remove(transition);
return;
}
Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
" was not on the state's transition list");
}

/// <summary>
/// 根据转换返回状态ID
/// </summary>
public StateID GetOutputState(Transition trans)
{
int transition = (int)trans;
if (m_Map.ContainsKey(transition))
{
return m_Map[transition];
}
return StateID.NullStateID;
}

/// <summary>
/// 用于进入状态前,设置进入的状态条件
/// 在进入当前状态之前,FSM系统会自动调用
/// </summary>
public virtual void DoBeforeEntering() { }

/// <summary>
/// 用于离开状态时的变量重置
/// 在更改为新状态之前,FSM系统会自动调用
/// </summary>
public virtual void DoBeforeLeaving() { }

/// <summary>
/// 用于判断是否可以转换到另一个状态,每帧都会执行
/// </summary>
public abstract void CheckTransition(GameObject player, GameObject npc);

/// <summary>
/// 控制NPC行为,每帧都会执行
/// </summary>
public abstract void Act(GameObject player, GameObject npc);

}


/// <summary>
/// FSMSystem类
/// 持有一个状态集合
/// </summary>
public class FSMSystem
{
private List<FSMState> m_States;

// 改变FSM状态的唯一方式是触发转换
// 不要直接改变状态
private StateID currentStateID;
public StateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }

public FSMSystem()
{
m_States = new List<FSMState>();
}

/// <summary>
/// 添加新的状态
/// </summary>
public void AddState(FSMState state)
{
if (state == null)
{
Debug.LogError("FSM ERROR: Null reference is not allowed");
}

if (m_States.Count == 0)
{
m_States.Add(state);
currentState = state;
currentStateID = state.ID;
return;
}

foreach (FSMState s in m_States)
{
if (s.ID == state.ID)
{
Debug.LogError("FSM ERROR: Impossible to add state " + state.ID.ToString() +
" because state has already been added");
return;
}
}
m_States.Add(state);
}

/// <summary>
/// 删除状态
/// </summary>
public void DeleteState(StateID id)
{
if (id == StateID.NullStateID)
{
Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
return;
}

foreach (FSMState state in m_States)
{
if (state.ID == id)
{
m_States.Remove(state);
return;
}
}
Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
". It was not on the list of states");
}

/// <summary>
/// 通过转换,改变FSM的状态
/// </summary>
public void PerformTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
return;
}
//获取转换对应的状态ID
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;
}

// 更新当前状态ID,currentStateID
currentStateID = id;
foreach (FSMState state in m_States)
{
if (state.ID == currentStateID)
{
// 离开状态时的变量重置
currentState.DoBeforeLeaving();
// 更新当前状态currentState
currentState = state;
// 进入状态前,设置进入的状态条件
currentState.DoBeforeEntering();
break;
}
}
}
}

应用实例

在Unity下开发,如果目标距离带有此脚本的GameObject小于一定的距离,GameObject将开始追踪目标,否则,带有此脚本的GameObject将按照路径点巡逻。

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class NPCControl : MonoBehaviour
{
public GameObject m_Player;
public Transform[] m_Path;
private FSMSystem m_FSM;

public void SetTransition(Transition t) { m_FSM.PerformTransition(t); }

public void Start()
{
MakeFSM();
}

public void FixedUpdate()
{
m_FSM.CurrentState.CheckTransition(m_Player, gameObject);
m_FSM.CurrentState.Act(m_Player, gameObject);
}

// NPC有两个状态: FollowPath(沿着路径巡逻) 和 ChasePlayer(追寻玩家)
// 当在FollowPath状态时,SawPlayer转换被触发时,将变为ChasingPlayer状态
// 当在ChasePlayer状态时,LostPlayer转换被触发,将变为FollowPath状态
private void MakeFSM()
{
FollowPathState follow = new FollowPathState(m_Path);
follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);

ChasePlayerState chase = new ChasePlayerState();
chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath);

m_FSM = new FSMSystem();
m_FSM.AddState(follow);
m_FSM.AddState(chase);
}
}

/// <summary>
/// FollowPath(沿着路径巡逻)
/// </summary>
public class FollowPathState : FSMState
{
private int currentWayPoint;
private Transform[] waypoints;

public FollowPathState(Transform[] wp)
{
waypoints = wp;
currentWayPoint = 0;
stateID = StateID.FollowingPath;
}

public override void CheckTransition(GameObject player, GameObject npc)
{
//RaycastHit hit;
//if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15f))
//{
// if (hit.transform.gameObject.CompareTag("Player"))
// npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
//}

// 当Player距离NPC小于15米时,触发SawPlayer状态
Collider[] colliders = Physics.OverlapSphere(npc.transform.position, 5f);
if(colliders.Length <= 0)
{
return;
}
// 需要设置场景中Player的Tag为Player
if (colliders[0].transform.gameObject.CompareTag("Player"))
npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer);
}

public override void Act(GameObject player, GameObject npc)
{
// 沿着路径点巡逻
Rigidbody rigidbody = npc.GetComponent<Rigidbody>();
Vector3 vel = rigidbody.velocity;
// 计算移动方向
Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;
// 如果距离小于1,前往下一个路径点
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);
}
rigidbody.velocity = vel;
}

}

/// <summary>
/// ChasePlayer(追寻玩家)
/// </summary>
public class ChasePlayerState : FSMState
{
public ChasePlayerState()
{
stateID = StateID.ChasingPlayer;
}

public override void CheckTransition(GameObject player, GameObject npc)
{
// 如果玩家距离NPC超出30米的距离,触发LostPlayer转换
if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30)
npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer);
}

public override void Act(GameObject player, GameObject npc)
{
Rigidbody rigidbody = npc.GetComponent<Rigidbody>();
Vector3 vel = 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;
rigidbody.velocity = vel;
}

}

工程地址:

链接:https://pan.baidu.com/s/1H07NQYw-gqDOXWaWh-oFPw 
提取码:dqse 
 

标签:状态,StateID,C#,FSM,通俗易懂,状态机,state,npc,public
From: https://blog.51cto.com/u_6871414/5897028

相关文章