c#扩展包-Stateless
准备
Stateless是一个有限状态机扩展包。在c#项目中可以直接通过NuGet安装。
使用他需要先用枚举写好你所有可能的状态和子状态。
例如移动,下蹲,空闲,跳跃,游泳,奔跑,走路。
其中,奔跑和走路是移动的子状态。
然后需要写触发器。所有状态转换必须要一个触发器。
所以你需要把所有的时机都精确描述,并且哪怕只有一个地方用到也要描写。
class Player
{enum State { 移动, 下蹲, 空闲, 空中, 二段跳, 游泳, 奔跑, 走路 }enum Trigger { 上, 下, 左, 右, 松开下, 落地, 落水, 出水, 跌落 }private StateMachine<State, Trigger> stateMachine = new StateMachine<State, Trigger>(State.空闲);
}
配置状态机
对状态机调用Configure会启用对这个状态下的配置。
从这个配置上调用的配置方法全部都会再返回这个状态的配置。
需要以此法对所有的状态都进行配置。
public Player(){stateMachine.Configure(State.空闲).Permit(Trigger.下, State.下蹲).Permit(Trigger.右, State.走路).Permit(Trigger.左, State.走路);stateMachine.Configure(State.下蹲).Permit(Trigger.松开下, State.空闲);}
触发器,目标,条件。
配置的方法有很多排列组合出来的版本,例如
- Permit
- PermitIf
- PermitDynamic
- PermitDynamicIf
一个基本的Permit方法接受两个参数,第一个是触发器,第二个是触发以后要变成谁。
例如空闲在触发了下的情况下会变成下蹲。
Permit(Trigger.下, State.下蹲)
Dynamic
如果有Dynamic,那么第二个参数会是一个委托,你可以动态的决定要变成谁。
PermitDynamic(Trigger.下, () => Guid.NewGuid() > Guid.NewGuid() ? State.下蹲 : State.空闲)
例如这句代码中,有50%概率变成下蹲,有50%概率变成空闲。
If
如果有If,那么还有第三个参数。也是一个委托,表示条件,需要你返回bool。
PermitIf(Trigger.下, State.下蹲, () => Guid.NewGuid() > Guid.NewGuid())
只有条件满足的时候,这个转化才能成功。
当一次触发时,会判断所有的转化。如果有多个方案可以通过(即便目标相同),会报错。
- 有多个相同的方案
- 一个方案存在的时候注册了他的If版本,并且If通过。
- 有多个If版本的方案种都通过了。
切换的目标
按照目标分组有以下分类
- Permit
- PermitReentry
- InternalTransition
- InitialTransition
Permit基础的转换,自选目标。
PermitReentry重新进入自己,意义是触发一次自己的退出和进入方法。
和直接Permit填自己是一样的。
InternalTransition不会发生切换,也不会触发退出和进入。
取而代之的是给定一个动作委托,触发你的动作。
使得看起来像因为切换状态发生了什么事情。
.InternalTransition(Trigger.上, () => { Console.WriteLine("在蹲下的时候不能跳"); })
InitialTransition指定一个子状态,表示这种状态的默认情况。
指定了以后就不会直接停留在这个状态身上了,一旦回来就会切换走。
例如走路和奔跑是移动的子状态。
为移动注册初始子状态为走路,那么以任何方式切换到移动都会变为走路。
包括从走路切到移动。
忽略和未定义的触发
Ignore方法可以忽略这个触发器,什么也不发生。
那如果声明忽略而调用这个转换会怎么样呢?会报一个错,表示这个转换没有注册。
你可以对状态机(不是配置)注册一个委托,来表示有未注册的转换时什么也不做。
stateMachine.OnUnhandledTrigger((s, t) => { });
有参数的转换
有时候,你从外部获取了输入,例如鼠标的位置。这种情况下不能用枚举来覆盖所有情况。
你需要对状态机调用SetTriggerParameters方法,然后保存这个返回值,之后要用到他。
var upTrigger = stateMachine.SetTriggerParameters<int>(Trigger.上);
只能在有If的情况下使用这个东西进行配置。
因为如果你没有条件,那么这个参数是没有意义的。
.PermitIf(upTrigger, State.下蹲, i => i > 4);
使用刚刚获得的东西作为触发器,那么你的条件委托就能使用他的参数。
给状态机触发器
使用Fire方法传递给状态机触发器。
他会根据配置进行转换状态。
如果你使用注册了参数的触发器,你还可以传递参数。
stateMachine.Fire(Trigger.出水);
stateMachine.Fire(upTrigger, 10086);
注册进入和退出
OnEntry方法注册进入这个状态时会发生什么事情。
OnExit方法注册退出这个状态时会发生什么事情。
OnEntryFrom版本的方法。表示从xx触发器来,使用注册了参数的触发器就能读取参数。
.OnEntry(() => { Console.WriteLine("进入到下蹲"); })
.OnExit(() => { Console.WriteLine("从下蹲退出"); })
.OnEntryFrom(Trigger.出水, () => { })
.OnEntryFrom(upTrigger, i => Console.WriteLine("携带的参数是" + i));
子级状态
一个状态可以声明为另一个状态的子状态(只能声明一个直接父状态)。
stateMachine.Configure(State.二段跳).SubstateOf(State.空中);stateMachine.Configure(State.奔跑).SubstateOf(State.移动);stateMachine.Configure(State.走路).SubstateOf(State.移动);
如果只涉及子状态改变,状态是同一个,那么不会执行父状态的进入和退出。
例如说,从走路切换到奔跑,就移动而言是没有变化的。
如果从子状态越过父状态切换到了其他状态,那么他们的退出都会执行。
例如从走路切换到空闲。那么此时也不能算作移动状态,所以移动也会一起结束。
bool b1 = stateMachine.IsInState(State.移动);
bool b2 = stateMachine.State == State.移动;
可以使用IsInState方法带父子级进行状态检测。
例如如果当前是走路,那么b1是true,因为走路是移动。
b2是false,他是直接比较的。
异步任务
带有Async的方法可以把注册的委托改为异步形式。
StateMachine<string, int> st = new StateMachine<string, int>("1");
st.Configure("1").Permit(2, "2").Permit(3, "3");st.Configure("2").OnEntryFromAsync(2, async () =>{await Task.Delay(100);await Console.Out.WriteLineAsync("进入2的第一个异步");await Task.Delay(100);await Console.Out.WriteLineAsync("进入2的第二个异步");}).OnEntryFrom(3, () => Console.WriteLine("进入2的同步")).Permit(1, "1").Permit(3, "3").OnExit(() => Console.WriteLine("离开2"));st.Configure("3").OnEntry(() => Console.WriteLine("进入3")).Permit(1, "1").Permit(3, "2");var t = st.FireAsync(2);
_ = st.FireAsync(3);
_ = st.FireAsync(1);
await t;
Console.WriteLine(st.State);
会有以下几个影响
- 在异步期间,所有的输入会排队,直到异步完成再依次执行
- 如果不使用Async后缀的方法,注册的委托也可以是异步的。状态机会认为他是普通方法,而程序会在后台把异步继续下去。
- 如果转换的源状态的退出,或是目标状态的进入有异步任务,那么就要使用
FireAsync- 即便使用了
OnEntryFromAsync携带条件,并且没有通过此条件,也会报错。 - 可以使用
Fire的转换,可以使用FireAsync而不会报错。
- 即便使用了
- 如果使用了任何通用转换的异步,那么所有转换都要使用
FireAsync- OnTransitionCompletedAsync
- OnTransitionedAsync
- OnUnhandledTriggerAsync
先后顺序
- 从子级到父级,依次经过所有Exit。
- 执行转换间隙。
- 从父级到子级,依次执行Entry,带参数的Entry,进入下一级。
- 完成切换后执行转换完成。
StateMachine<string, int> st = new StateMachine<string, int>("1.1");st.Configure("1").OnExit(() => Console.WriteLine("从1退出"));st.Configure("1.1").SubstateOf("1").Permit(0, "2.1").OnExit(() => Console.WriteLine("从1.1退出"));st.Configure("2").OnEntry(() => Console.WriteLine("进入2")).OnEntryFrom(0, () => Console.WriteLine("通过0进入2"));st.Configure("2.1").OnEntry(() => Console.WriteLine("进入2.1")).OnEntryFrom(0, () => Console.WriteLine("通过0进入2.2")).SubstateOf("2");st.OnTransitioned(st => Console.WriteLine("完成所有退出"));
st.OnTransitionCompleted(st => Console.WriteLine("进入到目标状态"));st.Fire(0);
/*
从1.1退出
从1退出
完成所有退出
进入2
通过0进入2
进入2.1
通过0进入2.2
进入到目标状态
*/
示例
class Player
{enum State { 移动, 下蹲, 空闲, 空中, 二段跳, 游泳, 奔跑, 走路 }enum Trigger { 键盘输入, 落地, 落水, 出水, 跌落, 超时 }DateTime TimeOut;StateMachine<State, Trigger> stateMachine;StateMachine<State, Trigger>.TriggerWithParameters<Vector2> InputTrigger;public Player(){stateMachine = new StateMachine<State, Trigger>(State.空闲);InputTrigger = stateMachine.SetTriggerParameters<Vector2>(Trigger.键盘输入);stateMachine.OnUnhandledTrigger((s, t) => { });stateMachine.Configure(State.移动).PermitIf(InputTrigger, State.空闲, vec => vec == Vector2.Zero).PermitIf(InputTrigger, State.空中, vec => vec == Vector2.UnitY).PermitIf(InputTrigger, State.下蹲, vec => vec == -Vector2.UnitY).Permit(Trigger.跌落, State.空中).Permit(Trigger.落水, State.游泳);stateMachine.Configure(State.下蹲).PermitIf(InputTrigger, State.空闲, vec => vec == Vector2.Zero).PermitIf(InputTrigger, State.空中, vec => vec == Vector2.UnitY);stateMachine.Configure(State.空闲).PermitIf(InputTrigger, State.走路, vec => vec.Y == 0 && vec.X != 0).PermitIf(InputTrigger, State.空中, vec => vec == Vector2.UnitY).PermitIf(InputTrigger, State.下蹲, vec => vec == -Vector2.UnitY);stateMachine.Configure(State.空中).Permit(Trigger.落地, State.空闲).PermitIf(InputTrigger, State.二段跳, vec => vec == Vector2.UnitY);stateMachine.Configure(State.二段跳).SubstateOf(State.空中).IgnoreIf(InputTrigger, vec => vec == Vector2.UnitY);stateMachine.Configure(State.游泳).Permit(Trigger.出水, State.空中);stateMachine.Configure(State.奔跑).SubstateOf(State.移动);stateMachine.Configure(State.走路).SubstateOf(State.移动).PermitIf(Trigger.超时, State.奔跑, () => DateTime.Now - TimeOut > TimeSpan.FromMicroseconds(500))//如果最后更新时间超过500秒则说明纯按住了走路0.5秒.OnEntry(async () =>{TimeOut = DateTime.Now;await Task.Delay(500);//在0.5秒延迟后尝试改为奔跑stateMachine.Fire(Trigger.超时);}).OnExit(() =>{TimeOut = DateTime.Now;});}
}
相关文章:
c#扩展包-Stateless
准备 Stateless是一个有限状态机扩展包。在c#项目中可以直接通过NuGet安装。 使用他需要先用枚举写好你所有可能的状态和子状态。 例如移动,下蹲,空闲,跳跃,游泳,奔跑,走路。 其中,奔跑和走路…...
Lua函数
--函数--无参无返回值 function F1()print("F1函数") end F1() print("*****************")--有参 function F2(a)print("F2函数"..a) end F2(2) --如果传入参数和函数数量不一致 --不会报错只是补空 F2(1,2) print("*****************&quo…...
左对齐和右对齐
%d默认为左对齐,%5d为左对齐(以空格补齐),%05d为左对齐(以0补齐),%-5d右补齐(以空格补齐),整数和小数同理。%.xf,x为小数点后保留的位数。 #include<stdi…...
高仿互站网站源码 后台手机端两套模板 电脑端二十套模版
高仿互站网 后台手机端两套模板 电脑端二十套模版,简单介绍几个功能, 支持用户注册开店 开店申请,支持用户发布自己商品 支持卡密形式或实物形式, 支持用户自己发布求助 任务大厅功能,源码完整 更多功能自己去发现吧…...
Spring Controller内存马
获取当前上下文运行环境 getCurrentWebApplicationContext WebApplicationContext context ContextLoader.getCurrentWebApplicationContext(); 在SpringMVC环境下获取到的是一个XmlWebApplicationContext类型的Root WebApplicationContext: 在Spring MVC环境中…...
Mysql004:用户管理
前言:本章节讲解的是mysql中的用户管理,包括(管理数据用户)、(控制数据库的访问权限)。 目录 1. 查询用户 2. 创建用户 3. 修改用户密码 4. 删除用户 5. 权限控制 1. 查询用户 在mysql数据库中࿰…...
计算机视觉与深度学习 | 视觉里程计(Visual Odometry,VO)研究现状
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 视觉里程计(Visual Odometry,VO) 研究背景及意义视觉里程计国内外研…...
Mojo:新型AI语言中的7个令人惊叹的Python升级,用简单的英语解释人工智能
Mojo:新型AI语言中的7个令人惊叹的Python升级 编程之美 用简单的英语解释人工智能 编程之美 由Coding Beauty设计的图像,使用Mojo标志和Python标志。 它比C更快,与Python一样简单,但速度提高了35000倍。 进入Mojo:一种…...
TCP连接的三次握手与四次挥手【重点】
TCP的运输连接管理概述 TCP是面向连接的协议,它基于运输连接来传送TCP报文段 TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程 TCP运输连接有以下三个阶段 TCP的运输连接管理就是使运输连接的建立和释放都能正常的进行 TCP建立连接的三次握手&a…...
重生奇迹MU新手玩家如何快速熟悉游戏
新手玩家必须掌握什么呢,建议新手玩家去官方网站上面看看游戏的详细介绍,必须要好的熟悉游戏的各种玩法,让玩家可以有一个初步的认识。 所以这方面是非常重要的,建议每位重生奇迹MU玩家都应该注重这些东西。下面我们就来简单介绍…...
MySQL 用户权限和远程访问设置
目录 一、用户操作查看当前拥有用户创建用户修改用户密码删除用户给root用户开放外网访问 二、用户权限操作授予权限的原则查看授予用户的权限给用户添加权限回收权限 一、用户操作 先要使用root用户登录MySQL后在执行后面操作 查看当前拥有用户 SELECT host,user,Grant_pri…...
Golang基础之关键字
Type 参考 ## https://blog.csdn.net/SHELLCODE_8BIT/article/details/122837699 type有如下几种用法: 定义结构体定义接口类型定义类型别名类型查询 类型定义 type Celsius float64 // 摄氏温度 type Fahrenheit float64 // 华氏温度const (AbsoluteZeroC Cels…...
DataFrame插入多列PerformanceWarning: DataFrame is highly fragmented.
DataFrame插入多列PerformanceWarning: DataFrame is highly fragmented. dataframe列比较多,增加列的代码如下: dfpd.DataFrame() for i in range(1000):vlist[]for j in range(1000):vlist.append(j) df[COL_ str(i)] vlistdf警告错误&#x…...
Springboot登录验证的统一拦截处理
在进行Springboot项目开发的时候如何把每次请求都要验证的用户进行提取拦截统一处理 背景 如果不进行统一的拦截处理,其实这是一个非常痛苦的一件事情,因为每次用户请求你都要去进行用户的信息(用户信息存储在session中)的验证&…...
自定义类型详解(上)
结构体 1 结构体的声明 1.1 结构的基础知识 结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.2 结构的声明 struct tag//struct是结构体的标志,tag是标签;名字。 {member-list;//成员变量 }variable-list;//变量列…...
【数据库——MySQL】(9)函数、查询练习及讲解
目录 1. 题目1.1 函数练习1.2 数据库查询 2. 解答2.1 函数练习2.2 数据库查询 1. 题目 1.1 函数练习 求圆周率的值,保留 6 位小数。生成两个 100 到 200 间的随机数。将”武汉大学”,”数学学院”,”计算数学”连接成一个字符串。求字符串中第三个字符为 A 的所有…...
【数据结构与算法——C语言】“串操作与算法”之“找出最长串及其长度”
目录 1. 实验内容及上机实验所用平台1.1 实验内容1.2 实验平台软件 2. 流程图3. 源代码4. 用例测试5. 实验总结 1. 实验内容及上机实验所用平台 1.1 实验内容 【问题描述】 给定两个字符串 s1 和 s2,求最长的 s1 前缀 ss 使得 ss 为 s2 的最长后缀,输出…...
泡泡玛特:一家中国潮玩品牌的出海之旅
泡泡玛特的出海之旅,可以为中国出海企业提供怎样的启示和借鉴? 中国潮玩品牌的出海之旅 如果在年轻人群体中聊起泡泡玛特,那么估计无人不知无人不晓。这家成立于2010年的潮玩企业,凭借琳琅满目让消费者爱不释手的创新产品…...
淘宝商品sku信息抓取接口api
在电商行业中,SKU是一个经常被使用的术语,但是对于很多人来说,这个词可能还比较陌生。在这篇文章中,我们将详细解释什么是SKU,以及在电商业务中它的作用和意义。 什么是SKU? SKU是“Stock Keeping Unit”…...
MySQL 多表关系(多表查询 一)
多表关系描述 MySQL是一种关系型数据库管理系统,它支持多表关系,这在数据库设计和查询中非常重要。 项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务…...
STM32CubeMX生成代码后,Keil编译烧写的那些“隐藏”步骤与调试器避坑
STM32CubeMX生成代码后,Keil编译烧写的那些“隐藏”步骤与调试器避坑 当你用STM32CubeMX生成代码后,以为万事大吉,结果在Keil里编译烧写时却频频碰壁——这几乎是每个STM32开发者都会经历的“成人礼”。那些教程里一笔带过的“编译”、“烧写…...
STM32CubeMX实战:5分钟搞定MAX31865 PT100测温,从SPI配置到温度读取全流程
STM32CubeMX实战:5分钟搞定MAX31865 PT100测温,从SPI配置到温度读取全流程 在工业测温领域,PT100凭借其优异的线性度和稳定性成为温度测量的黄金标准。而MAX31865作为专为RTD传感器设计的信号调理器,极大简化了前端电路设计。本文…...
百度网盘Mac版SVIP破解终极指南:解锁70倍下载速度的完整方案
百度网盘Mac版SVIP破解终极指南:解锁70倍下载速度的完整方案 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 百度网盘Mac版SVIP破解插件是一…...
3个步骤让你的外文漫画秒变中文:BallonsTranslator零门槛入门指南
3个步骤让你的外文漫画秒变中文:BallonsTranslator零门槛入门指南 【免费下载链接】BallonsTranslator 深度学习辅助漫画翻译工具, 支持一键机翻和简单的图像/文本编辑 | Yet another computer-aided comic/manga translation tool powered by deeplearning 项目地…...
简单三步让Windows焕然一新:Winhance中文版完整优化指南
简单三步让Windows焕然一新:Winhance中文版完整优化指南 【免费下载链接】Winhance-zh_CN A Chinese version of Winhance. C# application designed to optimize and customize your Windows experience. 项目地址: https://gitcode.com/gh_mirrors/wi/Winhance-…...
【ElevenLabs葡语语音实战指南】:20年AI语音工程师亲测的5大本地化避坑清单(附实测TTS自然度评分92.7%)
更多请点击: https://intelliparadigm.com 第一章:ElevenLabs葡语语音的核心技术架构与本地化本质 ElevenLabs 的葡语语音合成并非简单地在英语模型上叠加音素映射,而是基于多语言联合训练框架构建的端到端神经语音系统,其核心依…...
labelCloud架构解析:3D点云标注的模块化解决方案深度指南
labelCloud架构解析:3D点云标注的模块化解决方案深度指南 【免费下载链接】labelCloud A lightweight tool for labeling 3D bounding boxes in point clouds. 项目地址: https://gitcode.com/gh_mirrors/la/labelCloud 在3D计算机视觉和自动驾驶领域&#x…...
告别硬编码:模板引擎的加载逻辑与层叠继承艺术
更多内容请见: 《Python Web项目集锦》 - 专栏介绍和目录 文章目录 前言:被低估的视图半壁江山 第一章:破除迷信——Django 模板的设计哲学 1.1 限制的威力:为什么没有乘法器和复杂表达式? 1.2 两种角色的对立统一 第二章:寻宝游戏——模板加载器的底层引擎 2.1 TEMPLATE…...
保姆级教程:在Ubuntu 22.04上用ROS2 Humble和Gazebo搞定TurtleBot3仿真(从安装到建图导航)
保姆级教程:在Ubuntu 22.04上用ROS2 Humble和Gazebo搞定TurtleBot3仿真(从安装到建图导航) 机器人操作系统(ROS)正在重塑现代机器人开发流程。作为ROS2的最新长期支持版本,Humble Hawksbill为开发者带来了更…...
避坑指南:CCPD车牌数据集预处理中,OpenCV透视变换的3个常见错误与修复方法
CCPD车牌数据集预处理实战:透视变换的3个隐蔽陷阱与工业级解决方案 当你在深夜的显示器前反复调试CCPD数据集的预处理代码,却发现透视变换后的车牌图像像被无形之手扭曲——边框错位、字符拉伸、坐标偏移。这不是算法问题,而是OpenCV实战中那…...
