【unity框架开发14】状态机的封装与实现
文章目录
- 前言
- 有限状态机的主要作用和意义
- 状态机对象池
- 新增接口,标识拥有状态机的对象
- 新建状态基类,所有状态类的基类
- 状态机
- 使用案例
- 完结
前言
有限状态机以前的我嗤之以鼻,现在的我逐帧分析。其实之前我就了解过有限状态机,但是奈何那时能力不够,并不能理解其中的奥秘,只觉得麻烦。直到我项目需要越来越多的去编写敌人的AI,大量的if else让我头晕目眩,各种状态的切换和调试耗费我大量的时间。于是我又重新查找一些状态机的教程进行深入学习。以下我我的学习记录,希望对你有帮助。
如果后续项目使用时存在任何问题我还会回来补充和调整,文章的代码我也会尽量保持完整分享,以便大家可以复制粘贴到自己的项目中即可使用。
有限状态机的主要作用和意义
有限状态机(Finite State Machine,FSM)是一种在计算机科学和工程中常用的模型,用于描述对象或系统在有限状态集合中的行为和状态转换。它的主要作用和意义包括:
-
行为管理与控制: FSM通过定义有限数量的状态和状态之间的转换规则,可以有效管理和控制对象或系统的行为。每个状态代表对象可能处于的一种特定状态,例如待机、行走、攻击、受伤等,而状态之间的转换则定义了这些行为如何响应外部事件或条件变化。
-
简化复杂性: 将复杂的行为分解为简单的状态和状态转换,使得程序员可以更容易地理解和管理系统的行为逻辑。这种分解也有助于减少错误和提高代码的可维护性。
-
灵活性和扩展性: FSM可以根据具体需求进行灵活的定制和扩展。通过修改状态和状态转换规则,可以快速调整和扩展系统的行为,而无需大规模重构代码。
-
行为预测和调试: FSM的结构使得系统的行为预测变得相对容易,因为每个状态和转换的行为是明确定义的。这种结构也有助于调试和排查问题,因为可以更容易地追踪和理解系统在特定状态下的行为。
-
应用领域广泛: FSM不仅在游戏开发中常见,还在自动控制、工作流程管理、编程语言解析、通信协议等许多领域有着广泛的应用。其简单而强大的结构使得它成为许多复杂系统中行为管理的首选模型之一。
总之,有限状态机通过状态和状态转换的定义,提供了一种清晰且有效的方法来管理和控制对象或系统的复杂行为,为程序员和系统设计师提供了强大的工具,用于实现各种复杂的行为逻辑和控制流程。
状态机对象池
ObjectPoolData是对象池数据类,用于存储与对象池相关的数据和配置
/// <summary>
/// 对象池数据类
/// </summary>
public class ObjectPoolData
{
public Queue<object> PoolQueue = new Queue<object>(); // 对象队列
/// <summary>
/// 推送对象到池中
/// </summary>
/// <returns></returns>
public bool PushObj(object obj)
{
PoolQueue.Enqueue(obj);// 将对象加入队列
return true;
}
/// <summary>
/// 从池中获取对象
/// </summary>
/// <returns></returns>
public object GetObj() => PoolQueue.Dequeue();// 从队列中移除并返回对象
}
StateObjectPool 用于实现对象池模式,主要目的是高效管理和复用状态机中的对象(如状态实例)
/// <summary>
/// 状态机对象池
/// </summary>
public static class StateObjectPool
{
// 存储对象池数据的字典
private static readonly Dictionary<string, ObjectPoolData> poolDic = new Dictionary<string, ObjectPoolData>();
/// <summary>
/// 获取现有对象或创建新对象
/// </summary>
public static T GetOrNew<T>() where T : class, new()
{
string keyName = typeof(T).FullName;
T obj = null;
// 尝试从字典中获取对象池数据并检查是否有可用对象
if (poolDic.TryGetValue(keyName, out ObjectPoolData objectPoolData) && objectPoolData.PoolQueue.Count > 0)
{
obj = (T)objectPoolData.GetObj();// 返回池中的对象
}
else
{
obj = new T();
}
return obj;
}
/// <summary>
/// 将对象推送回对象池
/// </summary>
public static bool PushObject(object obj)
{
string keyName = obj.GetType().FullName;
if (!poolDic.TryGetValue(keyName, out ObjectPoolData poolData))
{
poolData = new ObjectPoolData();
poolDic[keyName] = poolData;
}
return poolData.PushObj(obj);// 将对象推入对象池
}
}
新增接口,标识拥有状态机的对象
IStateMachineOwner 接口用于标识拥有状态机的对象。它的主要作用是提供一个通用的接口,使得状态机可以引用它的拥有者,而不需要知道具体的实现细节。
/// <summary>
/// 提供一个通用的接口,用于标识拥有状态机的对象
/// </summary>
public interface IStateMachineOwner { }
新建状态基类,所有状态类的基类
StateBase 通常是状态机中每个具体状态的基类,它定义了所有状态共有的基本行为和接口。
/// <summary>
/// 状态基类,所有状态类的基类
/// </summary>
public abstract class StateBase
{
protected StateMachine stateMachine; // 状态机引用
/// <summary>
/// 初始化内部数据,系统使用
/// </summary>
/// <param name="stateMachine">状态机实例</param>
public void InitInternalData(StateMachine stateMachine)
{
this.stateMachine = stateMachine; // 设置状态机
}
/// <summary>
/// 初始化状态
/// 只在状态第一次创建时执行
/// </summary>
/// <param name="owner">宿主对象</param>
public virtual void Init(IStateMachineOwner owner) {}
/// <summary>
/// 反初始化
/// 当状态不再使用时调用,用于清理资源
/// 将一些引用置空,防止无法被垃圾回收
/// </summary>
public virtual void UnInit()
{
stateMachine = null; // 清空状态机引用
// 将当前状态对象放回对象池
StateObjectPool.PushObject(this);
}
/// <summary>
/// 状态进入
/// 每次进入该状态时都会执行
/// </summary>
public virtual void Enter() {}
/// <summary>
/// 状态退出
/// 当退出该状态时调用
/// </summary>
public virtual void Exit() {}
public virtual void Update() {}
public virtual void LateUpdate() {}
public virtual void FixedUpdate() {}
}
状态机
StateMachine 类是一个状态机的实现,用于管理和控制对象的不同状态。
/// <summary>
/// 状态机
/// </summary>
public class StateMachine
{
// 当前状态的类型
public Type CurrStateType { get; private set; } = null;
// 当前状态的实例
public StateBase currStateObj { get; private set; }
// 状态机的拥有者
private IStateMachineOwner owner;
// 所有的状态 Key:状态枚举的值 Value:具体的状态
private Dictionary<Type, StateBase> stateDic = new Dictionary<Type, StateBase>();
public Dictionary<string, object> stateShareDataDic;// 共享状态数据字典
/// <summary>
/// 初始化状态机
/// </summary>
/// <typeparam name="T">状态类型</typeparam>
/// <param name="owner">状态机的拥有者</param>
/// <param name="enableStateShareData">是否启用共享状态数据</param>
public void Init<T>(IStateMachineOwner owner, bool enableStateShareData = false) where T : StateBase, new()
{
if (enableStateShareData && stateShareDataDic == null) stateShareDataDic = new Dictionary<string, object>();
this.owner = owner;
ChangeState<T>();// 切换到指定状态
}
/// <summary>
/// 初始化状态机
/// </summary>
/// <param name="owner">状态机的拥有者</param>
/// <param name="enableStateShareData">是否启用共享状态数据</param>
public void Init(IStateMachineOwner owner, bool enableStateShareData = false)
{
if (enableStateShareData && stateShareDataDic == null) stateShareDataDic = new Dictionary<string, object>();
this.owner = owner;
}
/// <summary>
/// 切换状态
/// </summary>
/// <typeparam name="T">新状态的类型</typeparam>
/// <param name="reCurrstate">是否强制切换到当前状态</param>
/// <returns>切换是否成功</returns>
public bool ChangeState<T>(bool reCurrstate = false) where T : StateBase, new()
{
Type stateType = typeof(T);
// 状态一致,并且不需要刷新状态,则切换失败
if (stateType == CurrStateType && !reCurrstate) return false;
// 退出当前状态
if (currStateObj != null)
{
currStateObj.Exit();
MonoManager.Instance.RemoveUpdateListener(currStateObj.Update);
MonoManager.Instance.RemoveLateUpdateListener(currStateObj.LateUpdate);
MonoManager.Instance.RemoveFixedUpdateListener(currStateObj.FixedUpdate);
}
// 进入新状态
currStateObj = GetState<T>();// 获取新状态
CurrStateType = stateType;// 更新当前状态类型
currStateObj.Enter();// 进入新状态
MonoManager.Instance.AddUpdateListener(currStateObj.Update);
MonoManager.Instance.AddLateUpdateListener(currStateObj.LateUpdate);
MonoManager.Instance.AddFixedUpdateListener(currStateObj.FixedUpdate);
return true;
}
/// <summary>
/// 获取状态实例
/// </summary>
/// <typeparam name="T">状态类型</typeparam>
/// <returns>状态实例</returns>
private StateBase GetState<T>() where T : StateBase, new()
{
Type stateType = typeof(T);
// 尝试从字典中获取状态实例
if (stateDic.TryGetValue(stateType, out var st)) return st;
// 如果不存在,则创建新状态
StateBase state = StateObjectPool.GetOrNew<T>();
state.InitInternalData(this);// 初始化状态机引用
state.Init(owner);// 初始化状态
stateDic.Add(stateType, state);// 添加到状态字典
return state;
}
/// <summary>
/// 停止状态机
/// </summary>
public void Stop()
{
// 处理当前状态的额外逻辑
if (currStateObj != null)
{
currStateObj.Exit();// 退出当前状态
MonoManager.Instance.RemoveUpdateListener(currStateObj.Update);
MonoManager.Instance.RemoveLateUpdateListener(currStateObj.LateUpdate);
MonoManager.Instance.RemoveFixedUpdateListener(currStateObj.FixedUpdate);
currStateObj = null;// 清空当前状态对象
}
CurrStateType = null;// 清空当前状态类型
// 处理缓存中所有状态的逻辑
foreach (var state in stateDic.Values)
{
state.UnInit();// 反初始化状态
}
stateDic.Clear();// 清空状态字典
}
/// <summary>
/// 销毁状态机
/// </summary>
public void Destroy()
{
// 处理所有状态
Stop();
// 清除共享数据
stateShareDataDic?.Clear();
// 放弃所有资源的引用
owner = null;
// 将状态机实例放回对象池
StateObjectPool.PushObject(this);
}
}
使用案例
玩家脚本
public class PlayerController : MonoBehaviour, IStateMachineOwner
{
StateMachine stateMachine;
private void Start()
{
stateMachine = StateObjectPool.GetOrNew<StateMachine>();
//初始化时进入默认状态Idle
stateMachine.Init<PlayerIdleState>(this, true);
//初始化时不进入默认状态
// stateMachine.Init(this);
//初始化参数
stateMachine.stateShareDataDic["speed"] = 0;//速度
stateMachine.stateShareDataDic["attack"] = 10;//攻击力
stateMachine.stateShareDataDic["HP"] = 100;//血量
}
private void OnDestroy() {
//释放掉StateMachine的引用
stateMachine.Destroy();
}
}
待机状态
public class PlayerIdleState : StateBase
{
int speed;
int attack;
int HP;
Transform transform;
public override void Init(IStateMachineOwner owner)
{
transform = ((PlayerController)owner).transform;
}
public override void Enter()
{
//获取参数
speed = (int)stateMachine.stateShareDataDic["speed"];
attack = (int)stateMachine.stateShareDataDic["attack"];
HP = (int)stateMachine.stateShareDataDic["HP"];
Debug.Log("进入待机状态");
base.Enter();
}
public override void Exit()
{
Debug.Log("退出待机状态");
base.Exit();
}
public override void Update()
{
// 如果按下1
if (Input.GetKeyDown(KeyCode.Alpha1))
{
//切换到奔跑状态
stateMachine.ChangeState<PlayerRunState>();
}
Debug.Log($"玩家当前移动速度 {speed}");
Debug.Log($"玩家当前攻击力 {attack}");
Debug.Log($"玩家当前生命 {HP}");
Debug.Log("Update待机中");
base.Update();
}
public override void FixedUpdate()
{
Debug.Log("FixedUpdate待机中");
base.FixedUpdate();
}
public override void LateUpdate()
{
Debug.Log("LateUpdate待机中");
base.LateUpdate();
}
}
奔跑状态
public class PlayerRunState : StateBase
{
int speed;
int attack;
int HP;
Transform transform;
public override void Init(IStateMachineOwner owner)
{
transform = ((PlayerController)owner).transform;
}
public override void Enter()
{
//修改参数
stateMachine.stateShareDataDic["speed"] = 10;//速度
stateMachine.stateShareDataDic["attack"] = 10;//攻击力
stateMachine.stateShareDataDic["HP"] = 90;//血量
//获取参数
speed = (int)stateMachine.stateShareDataDic["speed"];
attack = (int)stateMachine.stateShareDataDic["attack"];
HP = (int)stateMachine.stateShareDataDic["HP"];
Debug.Log("进入奔跑状态");
base.Enter();
}
public override void Exit()
{
//修改参数
stateMachine.stateShareDataDic["speed"] = 0;
stateMachine.stateShareDataDic["attack"] = 0;
stateMachine.stateShareDataDic["HP"] = 0;
Debug.Log("退出奔跑状态");
base.Exit();
}
public override void Update()
{
// 如果按下2
if (Input.GetKeyDown(KeyCode.Alpha2))
{
//切换到待机状态
stateMachine.ChangeState<PlayerIdleState>();
}
Debug.Log($"玩家当前移动速度 {speed}");
Debug.Log($"玩家当前攻击力 {attack}");
Debug.Log($"玩家当前生命 {HP}");
Debug.Log("Update奔跑中");
base.Update();
}
public override void FixedUpdate()
{
Debug.Log("FixedUpdate奔跑中");
base.FixedUpdate();
}
public override void LateUpdate()
{
Debug.Log("LateUpdate奔跑中");
base.LateUpdate();
}
}
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~