关于人物或敌人的多种状态之间的切换,我们平常的做法就是定义个枚举,把所需的状态放在枚举里面,我们可能要在项目很多地方调用switch方法或者是设置人物状态。当状态比较多的时候,对状态的管理和获取也会变得很麻烦。所以这个时候,我们需要一个有限状态机来统一管理我们的状态(state)。
有限状态机包括两个部分:(1)状态集 FSMState,(2)状态管理机 FSMSystem.
一. 状态集FSMState代表一组状态的集合,一个游戏中可能有多个状态集,所以我们再设计FSMState时最好将它设计为抽象类,这样就能通过继承FSMState拥有一些公共的属性和方法,同时又有自己特定的状态。
二. 一个状态管理机主要的功能有这两点:
1.能够添加和删除状态集(FSMState).
2.能够切换和获取 某个状态集(FSMState)当前的状态(state).
我们还是用个小例子来解析一下:场景有两个物体,一个NPC,一个Player,NPC平时就是巡逻的状态,当Player距离NPC多近时NPC开始追逐Player,超过多远之后就继续巡逻。
下面上脚本:
状态机脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 为过度加入枚举标签,对应相应的状态
/// </summary>
public enum Transition
{
NullTransition=0, //用这个过渡来代表你的系统中不存在的状态
SawPlayer, //这里是NPC的两个过渡
LostPlayer
}
/// <summary>
/// 为状态加入枚举标签
/// </summary>
public enum StateID
{
NullStateID=0,
ChasingPlayer, //为配合NPC添加两个状态
FollowingPath
}
/// <summary>
/// 状态类
/// </summary>
public abstract class FSMState
{
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
/// <summary>
/// 为每一个子类初始化当前的状态
/// </summary>
protected StateID stateID;
public StateID ID
{
get { return stateID; }
}
/// <summary>
/// 添加过渡
/// </summary>
/// <param name="trans"></param>
/// <param name="id"></param>
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 stateID");
return;
}
//要知道这是一个确定的有限状态机(每个过渡对应一种状态,不能产生分支)
//检查当前的过渡是否已经在字典当中了
if(map.ContainsKey(trans))
{
Debug.LogError("FSMState error:There already has transition " + trans.ToString() + "check for another");
return;
}
map.Add(trans, id);
}
/// <summary>
/// 这个方法在状态地图中删除transition-stateid对
/// 如果过渡不存在地图中将打印一个错误
/// </summary>
/// <param name="trans"></param>
public void DeleteTransition(Transition trans)
{
//check for NullTransition
if(trans==Transition.NullTransition)
{
Debug.LogError("FSMState error:NullTransition is not allowed");
return;
}
//删除之前确认键值对是否存在于状态地图中
if(map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("FSMState error:Transition " + trans.ToString() + "passed to " + stateID.ToString() + "was not exist");
}
/// <summary>
/// 该方法在该状态接收到一个过渡时返回状态机需要成为的新状态
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
public StateID GetOutputState(Transition trans)
{
//检查一下map状态字典中是否存在这个过渡
if(map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}
/// <summary>
/// 这个方法用来设立进入状态前的条件
/// 在状态机分配它到当前状态之前他会被自动调用
/// </summary>
public virtual void DoBeforeEntering() { }
/// <summary>
/// 这个方法用来让一切都是必要的,例如在有限状态机变化到另一个时重置变量
/// 在状态机切换到新的状态之前它会被自动调用
/// </summary>
public virtual void DoBeforeLeaving() { }
/// <summary>
/// 要切换状态的条件,原因
/// NPC是被该类约束下对象的一个引用
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Reason(GameObject player, GameObject npc);
/// <summary>
/// 切换状态后对应的行动
/// 表现->该方法用来控制NPC在游戏世界中的行为
/// NPC的任何动作,移动或者交流都需要放置在这 ,
/// NPC是被该类约束下对象的一个引用
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Act(GameObject player, GameObject npc);
}
/// <summary>
/// 该类便是有限状态机类
/// 它持有NPC的状态集合,并且有添加,删除状态的方法,以及改变当前正在执行的状态
/// </summary>
public class FSMSystem
{
private List<FSMState> states;
//通过预装一个过渡的唯一方式来改变状态机的状态
//不要直接改变当前的状态
/// <summary>
/// 当前的状态ID
/// </summary>
private StateID currentStateID;
/// <summary>
/// 当前的状态类信息
/// </summary>
private FSMState currentFSMState;
public StateID CurrentStateID
{
get
{
return currentStateID;
}
}
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)
{
Debug.LogError("FSM error: There already exist " + s.ID.ToString());
return;
}
}
states.Add(s);
}
/// <summary>
/// 删除一个已存在状态机中的状态
/// 在它不存在时打印错误信息
/// </summary>
/// <param name="id"></param>
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 states)
{
if(state.ID==id)
{
states.Remove(state);
return;
}
}
Debug.LogError("FSM error:Can't delete state " + id.ToString() + ",it was not in list");
}
/// <summary>
/// 该方法基于当前状态和过渡状态是否通过来尝试改变状态机的状态,当当前状态没有目标状态用来过渡时则打印错误信息
/// 切换当前状态到下一个要转换的状态
/// </summary>
/// <param name="trans"></param>
public void PerformTransition(Transition trans)
{
//在改变当前状态前检测NullTransition
if(trans==Transition.NullTransition)
{
Debug.LogError("FSM error:NullTransition is not allowed");
return;
}
//在改变状态前检测当前状态是否可作为过渡的参数
StateID id = currentFSMState.GetOutputState(trans);
if(id==StateID.NullStateID)
{
Debug.LogError("FSM error: State " + currentFSMState.ID.ToString() + "does not allowed");
return;
}
//更新当前的状态机和状态编号
currentStateID = id;
foreach(FSMState state in states)
{
if(state.ID==currentStateID)
{
//在状态变为新状态前执行后处理
currentFSMState.DoBeforeLeaving();
currentFSMState = state;
//在状态可以使用Reason(动机)或者Act(行为)之前为它的决定条件重置它自己
currentFSMState.DoBeforeEntering();
break;
}
}
}
}
扫描二维码关注公众号,回复:
17599468 查看本文章

NPC逻辑与状态集继承脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class NPCController : MonoBehaviour
{
public GameObject player;
public Transform[] path;
private FSMSystem fsm;
public void SetTransition(Transition t)
{
//该方法用来改变有限状态机的状态,有限状态机基于当前的状态和通过的过渡状态
//如果当前的状态没有用来通过的过度状态,则会抛出错误
fsm.PerformTransition(t);
}
private void Start()
{
MakeFSM();
}
private void FixedUpdate()
{
fsm.CurrentFSMState.Reason(player, gameObject);
fsm.CurrentFSMState.Act(player, gameObject);
}
private void MakeFSM() //建造状态机
{
FollowPathState follow = new FollowPathState(path);
follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);
ChassPlayerState chass = new ChassPlayerState();
chass.AddTransition(Transition.LostPlayer, StateID.FollowingPath);
fsm = new FSMSystem();
fsm.AddState(follow);
fsm.AddState(chass);
}
}
public class FollowPathState : FSMState
{
private int currentWayPoint;
private Transform[] waypoints;
public FollowPathState(Transform[] wp)
{
waypoints = wp;
currentWayPoint = 0;
stateID = StateID.FollowingPath;
}
public override void DoBeforeEntering()
{
Debug.Log("Followingpath before entering");
}
public override void DoBeforeLeaving()
{
Debug.Log("Followingpath before leaving");
}
//要切换状态的条件
public override void Reason(GameObject player, GameObject npc)
{
RaycastHit hit;
if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15f))
{
if (hit.transform.gameObject.tag == "Player")
{
npc.GetComponent<NPCController>().SetTransition(Transition.SawPlayer);
}
}
}
//切换状态后的行动
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 class ChassPlayerState : FSMState
{
public ChassPlayerState()
{
stateID = StateID.ChasingPlayer;
}
public override void DoBeforeEntering()
{
Debug.Log("Followingpath before entering");
}
public override void DoBeforeLeaving()
{
Debug.Log("Followingpath before leaving");
}
//要切换状态的原因,条件
public override void Reason(GameObject player, GameObject npc)
{
if (Vector3.Distance(npc.transform.position, player.transform.position) > 3)
{
npc.GetComponent<NPCController>().SetTransition(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;
}
}
完成.