一、有限状态机介绍
有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。
状态机有 3 个组成部分:状态、事件、动作。
状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。
二、有限状态机的设计
设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;
主要分成了4个类文件:
1.状态机基类 FsmBase.cs
包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。
FsmBase.cs 代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
/// <summary>
/// 状态机基类
/// </summary>
public abstract class FsmBase
{
//状态机编号
public int FsmId { private set; get; }
//当前状态的类型
public sbyte CurrStateType;
public FsmBase(int fsmId)
{
FsmId = fsmId;
}
//关闭状态机
public abstract void ShutDown();
}
}
2.状态机类 Fsm.cs
状态机的具体类,继承了FsmBase类;内部实现了所有具体状态的管理、添加、更新、切换等。
Fsm.cs实现代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
//拥有者,拥有这个状态的类型
public class Fsm<T> : FsmBase where T : class
{
//拥有者
public T Owner { private set; get; }
//当前状态
private FsmState<T> m_CurrState;
//状态字典
private Dictionary<sbyte, FsmState<T>> m_dicState;
//参数字典(??切换状态的时候还传参数??)
private Dictionary<string, VariableBase> m_dicParam;
/*
* 构造函数 需要以下参数
* fsmId:状态机的id
* owner:拥有者
* states:状态机里的所有状态
*/
public Fsm(int fsmId, T owner, FsmState<T>[] states) : base(fsmId)
{
m_dicState = new Dictionary<sbyte, FsmState<T>>();
m_dicParam = new Dictionary<string, VariableBase>();
Owner = owner;
//把状态放入字典
int len = states.Length;
for (int i = 0; i < len; ++i)
{
FsmState<T> state = states[i];
if (null != state)
{
state.CurrFsm = this;
}
m_dicState[(sbyte)i] = state;
}
CurrStateType = -1;
}
//获取状态 by 状态类型
public FsmState<T> GetState(sbyte stateType)
{
FsmState<T> state = null;
m_dicState.TryGetValue(stateType,out state);
return state;
}
//状态机更新
public void OnUpdate()
{
if (null != m_CurrState)
{
m_CurrState.OnUpdate();
}
}
//状态机切换 by newStateType
public void ChangeState(sbyte newState)
{
//如果两个状态一致,不再进入
if (CurrStateType == newState)
return;
//先让当前状态执行OnLeave流程,执行上一个状态离开的逻辑
if (null != m_CurrState)
m_CurrState.OnLeave();
CurrStateType = newState;
m_CurrState = m_dicState[CurrStateType];
//然后再让新状态执行OnEnter流程,执行新状态的进入逻辑
m_CurrState.OnEnter();
}
//设置参数值
public void SetData<TData>(string key, TData value)
{
VariableBase itemBase = null;
if (m_dicParam.TryGetValue(key, out itemBase))
{
//参数原来存在,更新值
Variable<TData> item = itemBase as Variable<TData>;
item.Value = value;
//其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用
//m_dicParam[key] = item;
}
else
{
//参数原来不存在,插入
Variable<TData> item = new Variable<TData>();
item.Value = value;
m_dicParam[key] = item;
}
}
//获取参数值
public TData GetData<TData>(string key)
{
VariableBase itemBase = null;
if (m_dicParam.TryGetValue(key, out itemBase))
{
Variable<TData> item = itemBase as Variable<TData>;
if (null == item)
return default(TData);
return item.Value;
}
//返回该结构的实例?
return default(TData);
}
public override void ShutDown()
{
if (null != m_CurrState)
m_CurrState.OnLeave();
//循环遍历,执行所有状态的OnDestroy函数,执行销毁逻辑,销毁所有的状态
foreach (KeyValuePair<sbyte, FsmState<T>> kvPair in m_dicState)
{
if (null != kvPair.Value)
{
kvPair.Value.OnDestroy();
}
}
m_dicState.Clear();
m_dicParam.Clear();
}
}
}
3.状态机 状态抽象类 FsmState.cs
状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类FsmState ,具体的例子可以看测试代码中的实现。abstract改成接口也行,但是后来感觉没啥必要,有些派生类未必一定需要实现所有虚函数,所以就写成了抽象类,4个事件都写成了虚函数。派生类实现不实现都可。
FsmState.cs 代码实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
//状态机的状态
public abstract class FsmState<T> where T : class
{
//状态对应的状态机
public Fsm<T> CurrFsm;
//进入状态
public virtual void OnEnter() { }
//更新状态
public virtual void OnUpdate() { }
//离开状态
public virtual void OnLeave() { }
//状态机销毁时调用(销毁状态)
public virtual void OnDestroy() { }
}
}
4.状态机管理器 FsmManager.cs
状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。
FsmManager.cs代码实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
//状态机管理器
public class FsmManager : ManagerBase, IDisposable
{
//状态机字典
private Dictionary<int, FsmBase> m_dicFsm;
//状态机的临时编号
private int m_TemFsmId = 0;
public FsmManager()
{
m_dicFsm = new Dictionary<int, FsmBase>();
}
//初始化
public override void Init()
{
}
#region Create
/*
* 创建状态机实例
* T:拥有者类型
* fsmId:状态机Id
* owner:拥有者实例
* states:状态数组
*/
public Fsm<T> Create<T>(int fsmId, T owner, FsmState<T>[] states) where T : class
{
Fsm<T> fsm = new Fsm<T>(fsmId, owner, states);
m_dicFsm[fsmId] = fsm;
return fsm;
}
public Fsm<T> Create<T>(T owner, FsmState<T>[] states) where T : class
{
return Create(++m_TemFsmId, owner, states);
}
#endregion
#region 销毁状态机
//销毁状态机
public void DestroyFsm(int fsmId)
{
FsmBase fsm = null;
if (m_dicFsm.TryGetValue(fsmId, out fsm))
{
fsm.ShutDown();
m_dicFsm.Remove(fsmId);
}
}
#endregion
public void Dispose()
{
IEnumerator<KeyValuePair<int, FsmBase>> iter= m_dicFsm.GetEnumerator();
for (; iter.MoveNext();)
{
iter.Current.Value.ShutDown();
}
m_dicFsm.Clear();
}
}
}
三、测试与总结
因为刚开始写,还没办法在实际的工程中去做测试,所以主要通过按下按键就切换状态,测了一下状态切换是否符合预期,结果很好,符合预期。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Myh;
using YouYou;
using UnityEngine;
public class FmsTest : ITest
{
static private void PrintStateLog(int id, string strEvent)
{
Debug.Log("状态机:" + id + " 触发了:" + strEvent + " 事件");
}
#region 测试状态类
enum EnumTestClassType
{
eTest1,
eTest2,
eTest3,
eTest4,
}
class FsmTestState1 : FsmState<FmsTest>
{
public override void OnEnter()
{
base.OnEnter();
PrintStateLog(1, "OnEnter");
}
public override void OnUpdate()
{
base.OnUpdate();
PrintStateLog(1, "OnUpdate");
}
public override void OnLeave()
{
base.OnLeave();
PrintStateLog(1, "OnLeave");
}
public override void OnDestroy()
{
base.OnDestroy();
PrintStateLog(1, "OnDestroy");
}
}
class FsmTestState2 : FsmState<FmsTest>
{
public override void OnEnter()
{
base.OnEnter();
PrintStateLog(2, "OnEnter");
}
public override void OnUpdate()
{
base.OnUpdate();
PrintStateLog(2, "OnUpdate");
}
public override void OnLeave()
{
base.OnLeave();
PrintStateLog(2, "OnLeave");
}
public override void OnDestroy()
{
base.OnDestroy();
PrintStateLog(2, "OnDestroy");
}
}
class FsmTestState3 : FsmState<FmsTest>
{
public override void OnEnter()
{
base.OnEnter();
PrintStateLog(3, "OnEnter");
}
public override void OnUpdate()
{
base.OnUpdate();
PrintStateLog(3, "OnUpdate");
}
public override void OnLeave()
{
base.OnLeave();
PrintStateLog(3, "OnLeave");
}
public override void OnDestroy()
{
base.OnDestroy();
PrintStateLog(3, "OnDestroy");
}
}
class FsmTestState4 : FsmState<FmsTest>
{
public override void OnEnter()
{
base.OnEnter();
PrintStateLog(4, "OnEnter");
}
public override void OnUpdate()
{
base.OnUpdate();
PrintStateLog(4, "OnUpdate");
}
public override void OnLeave()
{
base.OnLeave();
PrintStateLog(4, "OnLeave");
}
public override void OnDestroy()
{
base.OnDestroy();
PrintStateLog(4, "OnDestroy");
}
}
#endregion
private Fsm<FmsTest> m_fsm=null;
public void OnTestStart()
{
FsmState<FmsTest>[] arrStates = new FsmState<FmsTest>[4];
arrStates[(int)EnumTestClassType.eTest1] = new FsmTestState1();
arrStates[(int)EnumTestClassType.eTest2] = new FsmTestState2();
arrStates[(int)EnumTestClassType.eTest3] = new FsmTestState3();
arrStates[(int)EnumTestClassType.eTest4] = new FsmTestState4();
m_fsm = GameEntry.FSM.Create(this, arrStates);
}
public void OnTestUpdate()
{
if (null == m_fsm)
return;
//m_fsm.OnUpdate();
if (Input.GetKeyDown(KeyCode.Keypad1))
{
m_fsm.ChangeState((int)EnumTestClassType.eTest1);
}
else if (Input.GetKeyDown(KeyCode.Keypad2))
{
m_fsm.ChangeState((int)EnumTestClassType.eTest2);
}
else if (Input.GetKeyDown(KeyCode.Keypad3))
{
m_fsm.ChangeState((int)EnumTestClassType.eTest3);
}
else if (Input.GetKeyDown(KeyCode.Keypad4))
{
m_fsm.ChangeState((int)EnumTestClassType.eTest4);
}
}
}