引言:近日忙于毕业论文,今天看到涨了3个粉丝,不甚惊喜。遂今日更新FSM有限状态机学习,希望大家共同进步!
开发版本:Unity 2017.1.1f1、VS 2017
适合人群:初学Unity者
一.有限状态机定义
有限状态机(英文:Finite State Machine,简称FSM),是指在不同阶段呈现不同的运行状态的系统,这些状态是有限的,不重叠的。这样的系统在某一时刻一定会处于其中所有状态中的一个状态,此时它接收一部分的输入,产生相应的响应,并且迁移到可能的状态。一般有限状态机有一下几种类型的动作:
1.进入动作:进入当前状态时执行2.退出动作:退出当前状态时执行3.输入动作:在当前状态时执行
4.转移动作:在进行特定切换状态时执行有限状态机提供了描述和控制应用逻辑的强大方法,可以应用于控制NPC行为,图形界面管理等方面。FSM可以使得各个状态之间相互独立,互不影响,避免了状态与状态之间的耦合度,具有规则简单,可读性和可验证性等优点。二.整理思路
简单的有限状态机就是switch语句,但如果状态过多的情况下,switch会使得代码显得臃肿,可扩展性很低。假设敌人有四种状态,Idle、Patrol、Chase、Attack,switch实现方法如下:public enum States { Idle,//空闲状态 Patrol,//巡逻状态 Chase,//追逐状态 Attack//攻击状态 } public class SimpleFSM : MonoBehaviour { private States currentState = States.Idle; private void Update() { switch (currentState) { case States.Idle: //TODO 空闲状态动作 break; case States.Patrol: //TODO 巡逻状态动作 break; case States.Chase: //TODO 追逐状态动作 break; case States.Attack: //TODO 攻击状态动作 break; } } }
接下来整理“高级的”有限状态机实现方法,FSM由状态类和管理类组成,先创建一个状态基类FSMState,用于实现状态类的基本方法,每添加一种状态,就作为它的子类。然后,用一个管理类FSMSystem把所有状态管理起来,方便使用状态机。FSM类图如下所示:
三.实现代码
1.定义枚举类型
public enum StateID { NullStateID, }
public enum Transition { NullTransition }
定义每一种状态的ID和状态之间的转换条件
2.FSMState基类
public abstract class FSMState { protected StateID stateID; public StateID ID { get { return this.stateID; } } protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>(); protected FSMSystem fsm; public FSMState(FSMSystem fsm) { this.fsm = fsm; } /// <summary> /// 添加转换条件 /// </summary> /// <param name="trans">转换条件</param> /// <param name="stateID">转换的目标状态</param> public void AddTransition(Transition trans, StateID stateID) { if (trans == Transition.NullTransition) { Debug.LogError(trans + "为空,转换条件不允许为空");return; } if (stateID == StateID.NullStateID) { Debug.LogError(stateID + "为空,状态ID不允许为空"); return; } if (map.ContainsKey(trans)) { Debug.LogError(trans + "已经存在,请查看该转换条件是否正确"); } else { map.Add(trans, stateID); } } /// <summary> /// 删除转换条件 /// </summary> /// <param name="trans">需删除的转换条件</param> public void DeleteTransition(Transition trans) { if (trans == Transition.NullTransition) { Debug.LogError(trans + "为空,转换条件不允许为空"); return; } if (map.ContainsKey(trans) == false) { Debug.LogError(trans + "不存在,请查看该转换条件是否正确"); } else { map.Remove(trans); } } /// <summary> /// 通过转换条件,得到目标状态 /// </summary> /// <param name="trans">转换条件</param> /// <returns>返回目标状态</returns> public StateID GetTargetStateID(Transition trans) { if (map.ContainsKey(trans) == false) { Debug.LogError(trans + "不存在,请查看该转换条件是否正确"); return StateID.NullStateID; } else { return map[trans]; } } public virtual void DoBeforeEntering() { }//进入动作 public virtual void DoAfterLeaving() { }//离开动作 public abstract void Act();//输入动作 public abstract void Reason();//转移动作 }
状态类主要存储转换条件和转换状态的字典,可以添加删除转换条件,根据条件返回对应的状态,并定义状态类的四种动作。
3.FSMSystem管理类
public class FSMSystem { private Dictionary<StateID, FSMState> states = new Dictionary<StateID, FSMState>(); private FSMState currentState; /// <summary> /// 更新当前状态行为 /// </summary> public void UpdateFSM() { currentState.Act(); currentState.Reason(); } /// <summary> /// 添加状态 /// </summary> /// <param name="state">需管理的状态</param> public void AddState(FSMState state) { if (state == null) { Debug.LogError(state + "为空"); return; } if (currentState == null) { currentState = state; } if (states.ContainsValue(state)) { Debug.LogError(state + "已经存在"); } else { states.Add(state.ID, state); } } /// <summary> /// 删除状态 /// </summary> /// <param name="id">需要删除状态的ID</param> /// /// <returns>删除成功返回true,否则返回false</returns> public bool DeleteState(StateID id) { if (id == StateID.NullStateID) { Debug.LogError(id + "为空"); return false; } if (states.ContainsKey(id) == false) { Debug.LogError(id + "不存在"); return false; } else { states.Remove(id); return true; } } /// <summary> /// 执行转换 /// </summary> /// <param name="trans">转换条件</param> public void PerformTransition(Transition trans) { if (trans == Transition.NullTransition) { Debug.LogError(trans + "为空");return; } StateID targetID = currentState.GetTargetStateID(trans); if (states.ContainsKey(targetID) == false) { Debug.LogError(targetID + "不存在");return; } FSMState targetState = states[targetID]; currentState.DoAfterLeaving(); targetState.DoBeforeEntering(); currentState = targetState; } }
FSMSystem用来管理各个状态,字典存储ID和对应的状态,可以添加删除状态,并执行状态转换。
四.案例讲解
假设敌人按照路线巡逻,当发现玩家的时候,开始追逐。但玩家跑出追击范围的时候,敌人继续回去巡逻。
项目的源文件在文末,有需要的童鞋可以自行下载。
敌人巡逻效果如下:
敌人发现玩家开始追逐,当玩家逃离后,返回继续巡逻,效果如下:
分析此时敌人状态有两种,巡逻和追逐,转换条件也对应有两种
public enum StateID { NullStateID, PatrolState, ChaseState }
public enum Transition { NullTransition, FindPlayer, LosePlayer }
创建两个子类PatrolState和ChaseState都继承自FSMState,分别用来实现巡逻和追逐的状态行为。
public class PatrolState : FSMState { private Transform enemy; private Transform player; private List<Transform> pathsList = new List<Transform>(); private int index = 0; public float smooth = 3; public PatrolState(FSMSystem fsm) : base(fsm) { stateID = StateID.PatrolState; enemy = GameObject.FindWithTag("Enemy").transform; player = GameObject.FindWithTag("Player").transform; Transform pathRoot = GameObject.Find("PathRoot").transform; foreach (Transform pathPoint in pathRoot) { pathsList.Add(pathPoint); } } public override void DoBeforeEntering() { Debug.Log("敌人开始巡逻了!"); } public override void DoAfterLeaving() { Debug.Log("敌人发现玩家,结束巡逻!"); } public override void Act() { Vector3 forward = pathsList[index].position - enemy.position; forward = new Vector3(forward.x, 0, forward.z); Quaternion targetQuaternion = Quaternion.LookRotation(forward, Vector3.up); enemy.rotation = Quaternion.Slerp(enemy.rotation, targetQuaternion, Time.deltaTime * smooth); enemy.Translate(Vector3.forward * Time.deltaTime * smooth); if (Vector3.Distance(enemy.position, pathsList[index].position) < 2f) { index++; index %= pathsList.Count; } } public override void Reason() { if (Vector3.Distance(enemy.position, player.position) < 4) { fsm.PerformTransition(Transition.FindPlayer); } } }
public class ChaseState : FSMState
{
private Transform enemy;
private Transform player;
public float smooth = 3;
public float chaseSpeed = 5;
public ChaseState(FSMSystem fsm):base(fsm)
{
stateID = StateID.ChaseState;
enemy = GameObject.FindWithTag("Enemy").transform;
player = GameObject.FindWithTag("Player").transform;
}
public override void DoBeforeEntering()
{
Debug.Log("敌人开始追逐玩家了!");
}
public override void DoAfterLeaving()
{
Debug.Log("敌人跟丢玩家,继续巡逻了!");
}
public override void Act()
{
Vector3 forward = player.position - enemy.position;
forward = new Vector3(forward.x, 0, forward.z);
Quaternion targetQuaternion = Quaternion.LookRotation(forward, Vector3.up);
enemy.rotation = Quaternion.Slerp(enemy.rotation, targetQuaternion, Time.deltaTime * smooth);
enemy.Translate(Vector3.forward * Time.deltaTime * chaseSpeed);
}
public override void Reason()
{
if (Vector3.Distance(enemy.position, player.position) > 8)
{
fsm.PerformTransition(Transition.LosePlayer);
}
}
}
在Enemy类中,实例化FSMSystem对象,添加巡逻和追逐状态,还有之间的转换条件
public class Enemy : MonoBehaviour
{
private FSMSystem fsm;
private void Start()
{
fsm = new FSMSystem();
FSMState patrolState = new PatrolState(fsm);
FSMState chaseState = new ChaseState(fsm);
fsm.AddState(patrolState);
fsm.AddState(chaseState);
patrolState.AddTransition(Transition.FindPlayer, StateID.ChaseState);
chaseState.AddTransition(Transition.LosePlayer, StateID.PatrolState);
}
private void Update()
{
fsm.UpdateFSM();
}
}
FSM有限状态机除了实现敌人的行为外,还可以用来管理UI界面的切换,使用方式基本相似。如果大神发现我分享的学习记录中有错误,还请不吝赐教。
案例百度云链接:链接:https://pan.baidu.com/s/1XYhmn3i9kwskai8_MEtAvg 密码:jchj
觉得我分享的学习记录不错的,点赞关注我哦!