【Unity/HFSM】使用UnityHFSM实现输入缓冲(预输入)和打断机制
文章目录
- 前言
- 预输入
- Animancer的InputBuffer:
- 在UnityHFSM中实现InputBuffer:
- 打断机制
前言
参考Animancer在状态机中的InputBuffer,在UnityHFSM中实现类似的InputBuffer机制,同时扩展一个状态打断机制
插件介绍:
Animancer:Unity的动画插件,易于修改和扩展。
UnityHFSM:Github上的一个开源分层状态机项目
基于继承的方法使用UnityHFSM:
UnityHFSM有非常方便的使用方法,可以在不创建新类的情况下创建状态和状态机,定义转换条件等,但如果状态逻辑比较复杂,并且有统一的父级行为,则建议使用类继承的方式定义状态和状态机类。
本文是笔者在做一个自己的ARPG小项目时做出来的,思路仅供参考。
预输入
Animancer的InputBuffer:
Animancer的InputBuffer主要处理流程如下:
- 在构造函数中绑定一个状态机
- 外部调用Buffer函数,传入期望的目标状态和超时时长
- InputBuffer在Update函数中轮询是否能够转换到目标状态,一旦转换成功即立刻结束轮询
- 超时后也会结束轮询
注意点:
- Animancer接收的是状态机接口,而不是具体的状态机类。但由于需要继承来扩展UntiyHFSM的状态机和状态行为的需要,我在实现时会传入的是类而非接口。
- Animancer的InputBuffer并未继承自Monobehavior,并且没有使用任何UnityEngine的东西,即Update需要在其他Mono类中调用,Time.deltaTime也要靠外部传入,方便起见,我实现时直接使用Time.deltaTime。
在UnityHFSM中实现InputBuffer:
Animancer的状态机有TryResetState函数,并且会返回是否成功,UntiyHFSM中无此函数,故使用StateMachine类的StateChanged来触发事件,并与期望的目标状态进行比对,一致则终止轮询。
Animancer直接使用TryResetState函数来改变状态,状态是否允许进入的条件也写在状态中,UnityHFSM依赖于Transition定义两个指定状态之间的转换,虽然也有RequestStateChange来强制转换到某个状态,但这样做并不优雅,并且可能出错。由于这个操作的多样性和复杂性,由外界传入Action来决定轮询时该进行的操作。(大多数时候使用UnityHFSM的StateMachine的Trigger函数来转换状态)
以下是代码,CharacterStateMachineBase是我扩展的角色状态机基类,ECharacterState是角色状态的枚举值
public class InputBuffer
{public bool IsActive => Action != null;public float TimeOut;public Action Action;public CharacterStateMachineBase StateMachine;public ECharacterState TargetState;public InputBuffer(CharacterStateMachineBase stateMachine){StateMachine = stateMachine;StateMachine.StateChanged += OnStateChanged;}~InputBuffer(){StateMachine.StateChanged -= OnStateChanged;}public void Buffer(Action action, ECharacterState targetState, float timeOut){Action = action;TargetState = targetState;TimeOut = timeOut;}public void Update(){if (IsActive){Action();TimeOut -= Time.deltaTime;if (TimeOut < 0)Clear();}}public virtual void Clear(){Action = null;TimeOut = default;}public void OnStateChanged(UnityHFSM.StateBase<ECharacterState> state){if (IsActive && state.name == TargetState){Clear();}}
}
在外部调用时如下
//CharacterBrain.cs//控制的玩家
public Character ControlledCharacter;//攻击缓冲
public float AttackTimeout = 1f;
InputBuffer AttackBuffer;//绑定操作
void Start()
{GameInput.Instance.onAttack += () =>{AttackBuffer.Buffer(() => ControlledCharacter.Attack(), ECharacterState.Attack, AttackTimeout);};
}//轮询
void Update()
{AttackBuffer.Update();
}
Character的Attack函数实际上就是在调用角色状态机的Trigger函数,如下
//Character.cspublic void Attack()
{if (Equipment.CurrentWeapon == null)return;StateMachine.Trigger("Attack");
}
打断机制
我把打断机制写在了状态机中,作为一种扩展行为,在UnityHFSM中,一个State有一个needsExitTime字段,该字段为true时,除非强制转换,否则无法退出本状态,配合Animancer的动画事件(OnEnd)可以很好的让动画来控制状态的转换(我这样做是希望角色的各种动作更加真实,让逻辑和表现更容易统一)。
为此我们只需要新增一个叫做CanExitBeforeEnd的bool变量即可表示本状态是否可以被打断。于此同时,实际上完全没有必要为每个状态新建一个CanExitBeforeEnd变量,将该变量放在状态机类中,供每个状态变更,供外界访问即可。
现在我们只使用CanExitBeforeEnd表示状态希望被改变,但没有直接改变的逻辑,假设我们当前使用Trigger的方法进行状态变更,则我们只需要在每次Trigger前查询一次当前状态是否需要变更,需要变更且状态的needsExitTime为true,则将其设置为false,再执行Trigger操作即可。
StateMachine的Trigger操作并不是虚函数,只需要在源码中为其添加virtual关键字即可,然后在扩展的CharacterStateMachineBase中重写Trigger逻辑,代码如下
public class CharacterStateMachineData
{public bool CanExitBeforeEnd;
}public class CharacterStateMachineBase : StateMachine<ECharacterState>
{public Character Owner;public CharacterStateMachineData Data;public CharacterStateMachineBase(Character owner, CharacterStateMachineData data = null) : base(){Owner = owner;if(data != null )Data = data;elseData = new CharacterStateMachineData();}//分层状态机递归获取具体状态public CharacterStateBase CurrentState{get{if (ActiveState is CharacterStateMachineBase)return (ActiveState as CharacterStateMachineBase).CurrentState;return ActiveState as CharacterStateBase;}}public override void Trigger(string trigger){//允许玩家通过一些动作和输入打断当前状态if(ActiveState.needsExitTime && Data.CanExitBeforeEnd)ActiveState.needsExitTime = false;base.Trigger(trigger);}
}public class CharacterStateBase : State<ECharacterState>
{public Character Owner;new public CharacterStateMachineBase fsm => base.fsm as CharacterStateMachineBase;public virtual Vector3 DeltaMotion => Owner.Animancer.Animator.deltaPosition;public CharacterStateBase(Character owner) : base(){Owner = owner;}public override void OnEnter(){base.OnEnter();fsm.Data.CanExitBeforeEnd = false;}
}
以上是在使用Trigger时打断的操作,假设我们使用的是普通的Transition进行状态变更,那么是无法打断的,比如如下的移动操作,完全没有使用Trigger
MovementStateMachineBase GroundFSM = new MovementStateMachineBase(owner);
GroundFSM.AddState(EMovementState.Idle, new IdleState(owner));
GroundFSM.AddState(EMovementState.Move, new MoveState(owner));
GroundFSM.AddTwoWayTransition(EMovementState.Idle, EMovementState.Move,t => Owner.Parameters.MovementDirection.magnitude > 0);
为此,我们可以在期望被打断的状态机中特别指出何时可以被打断,我当前只在攻击状态时可以被打断,不打断会放完攻击动画,打断会立刻进入其他状态并播放动画
特别的代码如下,该代码会在AttackState的Update函数中被轮询
Parameters是Character的字段,用于与直接的GameInput解耦
private void UpdateInterrupt()
{if(!fsm.Data.CanExitBeforeEnd)return;//移动打断if (Owner.Parameters.MovementDirection.magnitude > 0){needsExitTime = false;}
}
相关文章:
【Unity/HFSM】使用UnityHFSM实现输入缓冲(预输入)和打断机制
文章目录 前言预输入Animancer的InputBuffer:在UnityHFSM中实现InputBuffer: 打断机制 前言 参考Animancer在状态机中的InputBuffer,在UnityHFSM中实现类似的InputBuffer机制,同时扩展一个状态打断机制 插件介绍: A…...
Unity 圆形循环复用滚动列表
一.在上一篇垂直循环复用滚动列表的基础上,扩展延申了圆形循环复用滚动列表。实现此效果需要导入垂直循环复用滚动列表里面的类。 1.基础类 using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using …...
聚水潭数据无缝集成到金蝶云星空的实现方案
聚水潭数据集成到金蝶云星空:聚水潭调拨对接金蝶直接调拨ok 在企业信息化管理中,数据的高效流动和准确对接是实现业务流程顺畅运行的关键。本文将分享一个具体的系统对接集成案例——如何通过轻易云数据集成平台,将聚水潭的数据无缝集成到金…...
虚拟机断网没有网络,需清理内存,删除后再重启
进入NetworkManager可能没权限,设置权限777 to...
[c++11(二)]Lambda表达式和Function包装器及bind函数
1.前言 Lambda表达式着重解决的是在某种场景下使用仿函数困难的问题,而function着重解决的是函数指针的问题,它能够将其简单化。 本章重点: 本章将着重讲解lambda表达式的规则和使用场景,以及function的使用场景及bind函数的相关使…...
基于字节大模型的论文翻译(含免费源码)
基于字节大模型的论文翻译 源代码: 👏 star ✨ https://github.com/boots-coder/LLM-application 展示 项目简介 本项目是一个基于大语言模型(Large Language Model, LLM)的论文阅读与翻译辅助工具。它通过用户界面(…...
Mysql语法之DQL查询的多行函数
Mysql的多行函数和分组 目录 Mysql的多行函数和分组多行函数概念常用的多行函数 数据分组概念语法where和having的区别 语句关键字及执行顺序语句关键字执行顺序 实际操作基本语句格式和多行操作筛选语句格式 多行函数 概念 不管函数处理多少条,只返回一条记录&…...
OpenSSL 心脏滴血漏洞(CVE-2014-0160)
OpenSSL 心脏滴血漏洞(CVE-2014-0160) Openssl简介: 该漏洞在国内被译为"OpenSSL心脏出血漏洞”,因其破坏性之大和影响的范围之广,堪称网络安全里程碑事件。 OpenSSL心脏滴血漏洞的大概原理是OpenSSL在2年前引入了心跳(hearbea0机制来维特TS链接的…...
监控视频汇聚融合云平台一站式解决视频资源管理痛点
随着5G技术的广泛应用,各领域都在通信技术加持下通过海量终端设备收集了大量视频、图像等物联网数据,并通过人工智能、大数据、视频监控等技术方式来让我们的世界更安全、更高效。然而,随着数字化建设和生产经营管理活动的长期开展࿰…...
ElasticSearch 数据同步
1、同步调用 操作步骤: 管理系统新增酒店数据添加到数据库调用 ES 更新文档接口,同步数据库的数据到 ES 文档 流程图: 特点: 优点:实现简单,粗暴缺点:业务耦合度高 2、异步消息通知 操作步骤…...
MyBatis-Plus中isNull与SQL语法详解:处理空值的正确姿势
目录 前言1. 探讨2. 基本知识3. 总结 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 基本的Java知识推荐阅读: java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全&#x…...
RabbitMQ个人理解与基本使用
目录 一. 作用: 二. RabbitMQ的5中队列模式: 1. 简单模式 2. Work模式 3. 发布/订阅模式 4. 路由模式 5. 主题模式 三. 消息持久化: 消息过期时间 ACK应答 四. 同步接收和异步接收: 应用场景 五. 基本使用 ÿ…...
Python球球大作战
系列文章 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…...
入侵他人电脑,实现远程控制(待补充)
待补充 在获取他人无线网网络密码后,进一步的操作是实现入侵他人电脑,这一步需要获取对方的IP地址并需要制作自己的代码工具自动化的开启或者打开对方的远程访问权限。 1、获取IP地址(通过伪造的网页、伪造的Windows窗口、hook,信…...
数据分析实战—IMDB电影数据分析
1.实战内容 1.加载数据到movies_df,输出前5行,输出movies_df.info(),movies_df.describe() # (1)加载数据集,输出前5行 #导入库 import pandas as pd import numpy as np import matplotlib import matplotlib.pyplo…...
Google guava 最佳实践 学习指南之08 `BiMap`(双向映射)
guava 最佳实践 学习指南 Google Guava 库中的 BiMap(双向映射)是一种特殊的映射类型,它维护了映射的反向视图,并确保不存在重复值,且始终可以安全地使用值获取对应的键。以下是关于 Guava BiMap 的一些介绍和用法&am…...
【设计模式】空接口
(空)接口的用法总结 接口用于定义某个类的特定能力或特性。在工作流或任务管理系统中,接口可以帮助标识哪些任务可以在特定阶段执行。通过实现这些接口,任务类可以被标识为在相应的阶段可以执行,从而在验证和执行逻辑…...
Grad-CAM-解释CNN决策过程的可视化技术
Grad-CAM(Gradient-weighted Class Activation Mapping)是一种用于解释卷积神经网络(CNN)决策过程的可视化技术。其核心思想是通过计算分类分数相对于网络确定的卷积特征的梯度,来识别图像中哪些部分对分类结果最为重要…...
前后端学习中本周遇到的内容
一、RequiresPermissions注解 例如: RequiresPermissions("demo:staff:save") void saveStaff(); 权限控制,要求含有demo:staff:save的权限才能执行方法saveStaff()。 二、遇到的细节问题 在进行增删改查时,发送http请求时&…...
基于海思soc的智能产品开发(巧用mcu芯片)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 对于开发车规级嵌入式软件的同学来说,socmcu这样的组合,他们并不陌生。但是传统的工业领域,比如发动机、医疗或…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
sshd代码修改banner
sshd服务连接之后会收到字符串: SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢? 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头,…...
JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...
