文章目录
- 前言
- 有限状态机的主要作用和意义
- 状态机对象池
- 新增接口,标识拥有状态机的对象
- 新建状态基类,所有状态类的基类
- 状态机
- 使用案例
- 完结
前言
有限状态机以前的我嗤之以鼻,现在的我逐帧分析。其实之前我就了解过有限状态机,但是奈何那时能力不够,并不能理解其中的奥秘,只觉得麻烦。直到我项目需要越来越多的去编写敌人的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相关文章
- 【unity框架开发14】状态机的封装与实现
- 基于vue框架的的高校设备信息管理系统的设计与实现tx6d7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。-系统技术栈:
- 【Go后台开发第三课】实现goframe框架与emqx的通信连接及测试,实现物联网的开关控制应用
- RabbitMQ 优点和缺点- 消息可靠性:RabbitMQ 提供了持久化功能和消息确认机制,确保消息在各种情况下都能可靠地存储和处理。
灵活的路由:通过多种交换机类型和绑定规则,RabbitMQ 能够灵活地路由消息到指定的队列。
支持多种消息协议:实现了 AMQP 等(MQTT、STOMP)标准化、开放的消息队列协议,使其能够与多种语言编写的应用程序进行通信。
插件化扩展:RabbitMQ 提供了丰富的插件系统,可以通过插件扩展功能,如死信队列、压缩、追踪等。
高可用性:支持集群模式和镜像队列,确保服务的可用性
易用性和可管理性:提供了丰富的 API 和管理工具,以及多种客户端库和框架支持,易于集成和使用。
多语言支持:RabbitMQ 支持多种编程语言的客户端,包括 Java、Python、Ruby、C#、Node.js 等,方便开发人员集成到各种应用中。
高性能:在处理大量并发消息时表现出色。
广泛的社区支持:拥有庞大的开发者社区和丰富的文档资源。
劣势:
性能和吞吐量较低:相比于 Apache Kafka 等面向大数据流处理的消息队列系统,RabbitMQ 的吞吐量较低,不适合处理海量的实时数据流。RabbitMQ 的设计更注重消息的可靠性和灵活性,而非极高的吞吐性能。
- 跨平台移动开发 Xuijs超轻量级的框架 Dom与Event简洁代码实现文本展开收起
- Linux Kernel C语言编程范式
内部DSL是嵌入到开发语言内部,与开发语言混合使用的DSL,它可以是一个接口,如printf,也可以是一个宏,如下示例。UNUSUAL_DEV呈现了2种信息,一种是设备id_table信息,用于驱动匹配,一种是unusual_dev_list,用于标示非标准设备。具体设计和实现细节可以参考《Linux设备驱动框架设计》一文中的“USB块设备驱动框架设计”小节,不再赘述。
- 王家林的81门一站式云计算分布式大数据&移动互联网解决方案课程第14门课程:Android软硬整合设计与框架揭秘: HAL&Framework &Native Service &App&HTML5架构设计与实战开发
- 基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化
基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传
基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用
基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍
基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理
基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换
基于SqlSugar的开发框架循序渐进介
- 基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传
基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用
基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍
基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理
基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换
基于SqlSugar的开发框
- 基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换
基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传
基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用
基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍
基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理