一、前言
Hello,大家好,我是☆恬静的小魔龙☆,正所谓学而不思则罔,思而不学则殆,最近项目开发中,人物的动作特别多,用状态机去切换感觉太麻烦,然后切换的效果也并不理想。
比如下面的状态机:
每次“走→站立→跑”,都一些卡顿,没有那么丝滑,所以就想学习一下FSM(有限状态机)。
二、有限状态机
什么是有限状态机:
如其名有限状态机,就是可以枚举出有限个状态,然后状态直接可以进行切换的状态机。总体来说,有限状态机就是在不同阶段呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。这样的系统在某一时刻响应其状态中的一个状态。
为啥需要有限状态机:
在开发中,一个角色或者AI会有很多的状态,也会产生很多的动画,比如攻击、移动、巡逻、追击、逃跑、死完,这些动画状态会不停地切换,简单的方式就是使用Switch进行判断,然后每一个case去进行相应的状态处理和转换条件判断。
这种方式有点事简单方便,缺点就是不利于扩展,所以就出现了有限状态机去管理。
总结来说就是,为了解决游戏过于麻烦的状态转换
有限状态机与设计模式
细节依赖抽象,抽象不依赖细节,基于抽象编程,让框架先跑起来。
有限状态机参考的是状态设计模式的理念,允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,类的行为是基于它的状态改变的。
有限状态机,类比于Switch就是将cast中的逻辑封装到各个State对象中了,这样做可以方便扩展,后期如果需要增加新状态,只需要继承基类,添加实现就好,不用修改原来的代码,这就是开闭原则。
三、有限状态机的实现
3-1、有限状态机基类
首先,我们需要一个基类,让其他的State对象进行继承:
/// <summary> /// 状态抽象类 /// </summary> public abstract class FSMState { // 所属的状态机 protected FSMSystem m_FSM = null; protected FSMState(FSMSystem fsm) { m_FSM = fsm; } /// <summary> /// 进入状态 /// </summary> public virtual void OnEnter() { } /// <summary> /// 状态中进行的动作 /// </summary> public abstract void Action(); /// <summary> /// 检测状态转换 /// </summary> public abstract void Check(); /// <summary> /// 退出状态 /// </summary> public virtual void OnExit() { } }
3-2、有限状态机系统
接着,需要一个有限状态机系统去管理控制这些状态机,包含添加状态、删除状态、改变状态、状态改变等。
using System.Collections.Generic; using UnityEngine; /// <summary> /// 有限状态机系统 /// </summary> public class FSMSystem { /// <summary> /// 状态机所属游戏物体 /// </summary> public GameObject OwnerGo { get; private set; } public Animation Ani { get; private set; } // 用字典存储每个状态ID对应的状态 private Dictionary<StateID, FSMState> m_StateMap = new Dictionary<StateID, FSMState>(); public FSMSystem(GameObject ownerGo) { OwnerGo = ownerGo; Ani = OwnerGo.GetComponent<Animation>(); } /// <summary> /// 当前状态ID /// </summary> public StateID CurrentStateID { get; private set; } /// <summary> /// 当前状态 /// </summary> public FSMState CurrentState { get; private set; } /// <summary> /// 添加状态 /// </summary> /// <param name="id">添加的状态ID</param> /// <param name="state">对应的状态对象</param> public void AddState(StateID id, FSMState state) { // 如果当前状态为空,就设置为默认状态 if (CurrentState == null) { CurrentStateID = id; CurrentState = state; } if (m_StateMap.ContainsKey(id)) { Debug.LogErrorFormat("状态ID:{0}已经存在,不能重复添加!", id); return; } m_StateMap.Add(id, state); } /// <summary> /// 移除状态 /// </summary> /// <param name="id">要移除的状态ID</param> public void RemoveState(StateID id) { if (!m_StateMap.ContainsKey(id)) { Debug.LogWarningFormat("状态ID:{0}不存在,不需要移除", id); return; } m_StateMap.Remove(id); } /// <summary> /// 改变状态 /// </summary> /// <param name="id">需要转换到的目标状态ID</param> public void ChangeState(StateID id) { if (id == CurrentStateID) return; if (!m_StateMap.ContainsKey(id)) { Debug.LogErrorFormat("状态ID:{0}不存在!", id); return; } if (CurrentState != null) CurrentState.OnExit(); CurrentStateID = id; CurrentState = m_StateMap[id]; CurrentState.OnEnter(); } /// <summary> /// 更新,在状态机持有者物体的Update中调用 /// </summary> public void Update() { CurrentState.Action(); CurrentState.Check(); } }
3-3、添加状态
这时候,就需要看一下我们的模型有多少个动画了,比如我这个模型:
共12个动画片段,但是我只需要里面的9个动画片段,所以先定义一个枚举,将所有用到的动画片段枚举出来:
/// <summary> /// 状态ID /// </summary> public enum StateID { Stand, Standwait, Walk, Jump1, Run, Jump2, Down, Take, Work }
接着就添加状态:
/// <summary> /// 站立 /// </summary> public class StandState : FSMState { private float m_Timer = 0f; public StandState(FSMSystem fsm) : base(fsm) { Debug.Log("StandState"); } public override void Action() { m_FSM.Ani.Play("Stand"); } public override void Check() { m_Timer += Time.deltaTime; // 如果旋转达到1秒 if (m_Timer > 0.25f) { // 转换到移动状态 m_FSM.ChangeState(StateID.Standwait); } } public override void OnExit() { m_Timer = 0; } } /// <summary> /// 站立等待 /// </summary> public class StandWaitState : FSMState { public StandWaitState(FSMSystem fsm) : base(fsm) { Debug.Log("StandWaitState"); } public override void Action() { m_FSM.Ani.Play("Stand-wait"); } public override void Check() { } } /// <summary> /// 走 /// </summary> public class WalkState : FSMState { public WalkState(FSMSystem fsm) : base(fsm) { Debug.Log("WalkState"); } public override void Action() { m_FSM.Ani.Play("walk"); } public override void Check() { } } /// <summary> /// 小跳 /// </summary> public class Jump1State : FSMState { private float m_Timer = 0f; public Jump1State(FSMSystem fsm) : base(fsm) { Debug.Log("Jump1State"); } public override void Action() { m_FSM.Ani.Play("jump1"); } public override void Check() { m_Timer += Time.deltaTime; // 如果旋转达到1秒 if (m_Timer > 0.6f) { // 转换到移动状态 m_FSM.ChangeState(StateID.Standwait); } } public override void OnExit() { m_Timer = 0; } } /// <summary> /// 跑 /// </summary> public class Run2State : FSMState { public Run2State(FSMSystem fsm) : base(fsm) { Debug.Log("Run2State"); } public override void Action() { m_FSM.Ani.Play("run"); } public override void Check() { } } /// <summary> /// 大跳 /// </summary> public class Jump2State : FSMState { private float m_Timer = 0f; public Jump2State(FSMSystem fsm) : base(fsm) { Debug.Log("Jump2State"); } public override void Action() { m_FSM.Ani.Play("jump2"); } public override void Check() { m_Timer += Time.deltaTime; // 如果旋转达到1秒 if (m_Timer > 0.75f) { // 转换到移动状态 m_FSM.ChangeState(StateID.Standwait); } } public override void OnExit() { m_Timer = 0; } } /// <summary> /// 拿取 /// </summary> public class TakeState : FSMState { public TakeState(FSMSystem fsm) : base(fsm) { Debug.Log("TakeState"); } public override void OnEnter() { m_FSM.Ani.Play("take"); } public override void Action() { } public override void Check() { } public override void OnExit() { } } /// <summary> /// 工作 /// </summary> public class WorkState : FSMState { public WorkState(FSMSystem fsm) : base(fsm) { Debug.Log("WorkState"); } public override void OnEnter() { m_FSM.Ani.Play("work"); } public override void Action() { } public override void Check() { } public override void OnExit() { } } /// <summary> /// 蹲 /// </summary> public class DownState : FSMState { public DownState(FSMSystem fsm) : base(fsm) { Debug.Log("DownState"); } public override void OnEnter() { m_FSM.Ani.Play("Down"); } public override void Action() { } public override void Check() { } public override void OnExit() { } }
我为了方便,将有限状态机的基类、状态枚举、状态对象都写到一个脚本里面了,大家觉得不爽也可以分开来。
3-4、调用动画
新建一个Character类,用来调用动画:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Character : MonoBehaviour { // 有限状态机 private FSMSystem m_FSM = null; private void Start() { // 实例化有限状态机 m_FSM = new FSMSystem(this.gameObject); // 添加状态到有限状态机中 m_FSM.AddState(StateID.Stand, new StandState(m_FSM)); m_FSM.AddState(StateID.Standwait, new StandWaitState(m_FSM)); m_FSM.AddState(StateID.Walk, new WalkState(m_FSM)); m_FSM.AddState(StateID.Jump1, new Jump1State(m_FSM)); m_FSM.AddState(StateID.Run, new Run2State(m_FSM)); m_FSM.AddState(StateID.Jump2, new Jump2State(m_FSM)); m_FSM.AddState(StateID.Down, new DownState(m_FSM)); m_FSM.AddState(StateID.Take, new TakeState(m_FSM)); m_FSM.AddState(StateID.Work, new WorkState(m_FSM)); } private void Update() { // 调用有限状态机中的Update方法 if (m_FSM != null) m_FSM.Update(); if (Input.GetKey(KeyCode.Alpha1)) { m_FSM.ChangeState(StateID.Stand); } if (Input.GetKey(KeyCode.Alpha2)) { m_FSM.ChangeState(StateID.Standwait); } if (Input.GetKey(KeyCode.Alpha3)) { m_FSM.ChangeState(StateID.Walk); } if (Input.GetKey(KeyCode.Alpha4)) { m_FSM.ChangeState(StateID.Jump1); } if (Input.GetKey(KeyCode.Alpha5)) { m_FSM.ChangeState(StateID.Run); } if (Input.GetKey(KeyCode.Alpha6)) { m_FSM.ChangeState(StateID.Jump2); } if (Input.GetKey(KeyCode.Alpha7)) { m_FSM.ChangeState(StateID.Down); } if (Input.GetKey(KeyCode.Alpha8)) { m_FSM.ChangeState(StateID.Take); } if (Input.GetKey(KeyCode.Alpha9)) { m_FSM.ChangeState(StateID.Work); } } }
挂载到对象上:
效果图:
四、后言
这个例子没有太多炫技的代码,作为一个学习有限状态机的入门例子来看还是很不错的。
希望大家今天也有学习哦。