当前位置: 首页 > news >正文

Unity有限状态机的简易实现

本人嘴笨,不会说。
该代码实现一个功能较为齐全的有限状态机,可用于大部分的应用场景。
大致实现几个功能

  • 状态更新
  • 状态转换
  • 状态消息处理
  • 全局状态转换和反转状态(转换为前一个状态)

代码分为

  • 状态类
  • 状态管理类
  • 枚举(状态枚举和消息枚举)

简易通用框架代码

状态接口

using System;namespace FSM
{public interface IStateBase<T, TState, TMessage>where TState : Enumwhere TMessage : Enum{//确定该状态枚举public TState GetState();//进入状态void OnEnter(T owner);//更新状态void OnUpdate(T owner);//退出状态void OnExit(T owner);// 转换状态 (-1 为返回上一个状态,0 为保持不变,1 为转换为下一个状态)int OnTransfer(T owner, out TState newState);//消息接收void OnMessage(T owner, TMessage e);}
}

全局状态接口

using System;namespace FSM
{public interface IGlobalState<T, TState, TMessage> : IStateBase<T, TState, TMessage>where TState : Enumwhere TMessage : Enum{//全局转换状态bool OnGlobalTransfer(T owner);}
}

状态管理类

using System;
using System.Collections.Generic;
using UnityEngine;namespace FSM
{public class FsmMachine<T, TState, TMessage>where TState : Enumwhere TMessage : Enum{//状态类存储private Dictionary<TState, IStateBase<T, TState, TMessage>> _states =new Dictionary<TState, IStateBase<T, TState, TMessage>>();//全局状态类存储private Dictionary<TState, IGlobalState<T, TState, TMessage>> _globalStates =new Dictionary<TState, IGlobalState<T, TState, TMessage>>();//Fsm的使用对象private T _owner;//状态枚举private TState _stateID;//上一个状态private IStateBase<T, TState, TMessage> _previousState;//状态类private IStateBase<T, TState, TMessage> _state;//初始化public void Init(T owner, TState stateID){if (owner == null)throw new NullReferenceException();if (!_states.ContainsKey(stateID))throw new AggregateException(stateID + "不存在");_owner = owner;_stateID = stateID;_state = _states[stateID];_state.OnEnter(_owner);}//注册状态类public void RegisterState(IStateBase<T, TState, TMessage> state){if (_states == null)_states = new Dictionary<TState, IStateBase<T, TState, TMessage>>();if (_states.ContainsKey(state.GetState()))Debug.Log(state.GetState() + "已存在");_states.Add(state.GetState(), state);}//删除状态类public void RemoveState(TState stateID){if (_states == null || !_states.ContainsKey(stateID))return;_states.Remove(stateID);}//注册全局状态类public void RegisterGlobalState(IGlobalState<T, TState, TMessage> globalState){if (_globalStates == null)_globalStates = new Dictionary<TState, IGlobalState<T, TState, TMessage>>();if (_globalStates.ContainsKey(globalState.GetState()))Debug.Log(globalState.GetState() + "已存在");_globalStates.Add(globalState.GetState(), globalState);RegisterState(globalState);}//删除全局状态类public void RemoveGlobalState(TState stateID){if (_globalStates == null || !_globalStates.ContainsKey(stateID))return;_globalStates.Remove(_stateID);RemoveState(stateID);}//更新状态public void Update(){if (_state == null)return;//状态更新_state.OnUpdate(_owner);//全局状态转换foreach (var globalState in _globalStates.Values){if (globalState.OnGlobalTransfer(_owner)){ChangeState(globalState.GetState());return;}}//状态转换switch (_state.OnTransfer(_owner, out TState stateID)){case -1:RevertToPreviousState();break;case 0://不做任何事break;case 1:ChangeState(stateID);break;}}//状态反转(返回上一个状态)private void RevertToPreviousState(){ChangeState(_previousState.GetState());}//转换状态public void ChangeState(TState stateID){if (_states == null || !_states.ContainsKey(stateID)) return;if (_stateID.Equals(stateID)) return;_state.OnExit(_owner);_stateID = stateID;_previousState = _state;_state = _states[stateID];_state.OnEnter(_owner);}}
}

实现代码

敌人枚举部分

public enum EnemyState
{None,//闲置Idle,//追逐Chase,
}public enum EnemyMessage
{None,//消息Log,
}

敌人状态基类

using FSM;namespace Enemy
{public abstract class EnemyStateBase : IGlobalState<EnemyEntity, EnemyState, EnemyMessage>{public abstract EnemyState GetState();public abstract void OnEnter(EnemyEntity owner);public abstract void OnUpdate(EnemyEntity owner);public abstract void OnExit(EnemyEntity owner);public abstract int OnTransfer(EnemyEntity owner, out EnemyState newState);public virtual bool OnGlobalTransfer(EnemyEntity owner) => false;public void OnMessage(EnemyEntity owner, EnemyMessage e){//可以通过虚方法的方式,让子类继承实现对应消息的方法if (e == EnemyMessage.Log)OnLog();}protected virtual void OnLog() { }}
}

实现了两个状态 Idle 和 Chase

using UnityEngine;namespace Enemy.State
{public class EnemyIdleState : EnemyStateBase{public override EnemyState GetState() => EnemyState.Idle;public override void OnEnter(EnemyEntity owner){owner.SetVelocity(Vector3.zero);}public override void OnUpdate(EnemyEntity owner) { }public override void OnExit(EnemyEntity owner) { }public override int OnTransfer(EnemyEntity owner, out EnemyState newState){if (Vector3.SqrMagnitude(owner.target.transform.position - owner.transform.position) <= owner.chaseRange * owner.chaseRange){newState = EnemyState.Chase;return 1;}newState = GetState();return 0;}}
}
using UnityEngine;namespace Enemy.State
{public class EnemyChaseState : EnemyStateBase{public override EnemyState GetState() => EnemyState.Chase;public override void OnEnter(EnemyEntity owner) { }public override void OnUpdate(EnemyEntity owner){Vector3 v = owner.target.transform.position - owner.transform.position;//靠太近就挺下来,但状态还是追逐if (Vector3.SqrMagnitude(v) < 1)owner.SetVelocity(Vector3.zero);elseowner.SetVelocity(v.normalized * owner.speed);}public override void OnExit(EnemyEntity owner) { }public override int OnTransfer(EnemyEntity owner, out EnemyState newState){if (Vector3.SqrMagnitude(owner.target.transform.position - owner.transform.position) > owner.escapeRange * owner.escapeRange){newState = EnemyState.Idle;return 1;}newState = GetState();return 0;}}
}

敌人部分

using Enemy;
using Enemy.State;
using FSM;
using UnityEngine;public class EnemyEntity : MonoBehaviour
{//移动速度public float speed = 2f;//追逐范围public float chaseRange = 5f;//逃脱范围public float escapeRange = 8f;//追逐目标public GameObject target;private Rigidbody _rigidbody;private FsmMachine<EnemyEntity, EnemyState, EnemyMessage> _machine;public void SetVelocity(Vector3 v3) => _rigidbody.velocity = v3;private void Awake(){ _rigidbody = GetComponent<Rigidbody>();//创建Fsm并添加状态_machine = new FsmMachine<EnemyEntity, EnemyState, EnemyMessage>();_machine.RegisterState(new EnemyIdleState());_machine.RegisterState(new EnemyChaseState());_machine.Init(this, EnemyState.Idle);}private void Update(){_machine.Update();}
}

玩家部分

using System;
using UnityEngine;public class PlayerEntity : MonoBehaviour
{public float speed = 4f;private void Update(){float hor = Input.GetAxis("Horizontal");float ver = Input.GetAxis("Vertical");Vector3 v = new Vector3(hor, 0, ver).normalized;transform.position += (speed * Time.deltaTime) * v;}
}

敌人主要实现追逐和闲置两个状态,玩家靠近就追逐,远离就闲置。
画面就不展示了,可以复制代码自己尝试

相关文章:

Unity有限状态机的简易实现

本人嘴笨&#xff0c;不会说。 该代码实现一个功能较为齐全的有限状态机&#xff0c;可用于大部分的应用场景。 大致实现几个功能 状态更新状态转换状态消息处理全局状态转换和反转状态&#xff08;转换为前一个状态&#xff09; 代码分为 状态类状态管理类枚举&#xff08;…...

什么是NetApp的DQP和如何安装DQP?

首先看看什么是DQP&#xff0c;DQPDisk Qualification Package&#xff0c;文字翻译就是磁盘验证包。按照NetApp的最佳实践&#xff0c;要定期升级DQP包&#xff0c;保证对最新磁盘和磁盘扩展柜的兼容。 本文主要介绍7-mode下如何升级DQP&#xff0c;至于cluster mode另外文章…...

Vue之Vue的介绍安装开发实例生命周期钩子

博主心得&#xff1a; keyup必须与change一起使用v-on.click可以直接写成clickclick“setVal”里的setVal换成数字之后有惊喜VS Code是真的狗&#xff0c;一些报错根本不会直接显示总结&#xff1a;VS code太狗了 1.vue介绍 1.1 什么是vue vue是一个构建用户界面UI的渐进式jav…...

【计网】计算机网络概述

目录 一、计算机网络的概念 二、计算机网络的组成 1、从组成部分上看 2、从工作方式上看 3、从功能组成上看 三、计算机网络的功能 1、数据通信 2、资源共享 3、分布式处理 4、提高可用性 5、负载均衡 四、计算机网络的分类 1、按分布范围 1.广域网 2.城域网 3.…...

初识Java 14-1 测试

目录 测试 单元测试 JUnit 测试覆盖率 前置条件 断言 Java提供的断言语法 Guava提供的更方便的断言 契约式设计中的断言 DbC 单元测试 Guava中的前置条件 本笔记参考自&#xff1a; 《On Java 中文版》 测试 ||| 如果没有经过测试&#xff0c;代码就不可能正常工作…...

react常用的hooks有哪些?

React常用的Hooks包括以下几种&#xff1a; 1.useState&#xff1a;用于在函数组件中创建和管理状态。它返回一个数组&#xff0c;第一个值是当前状态的值&#xff0c;第二个值是更新状态的函数。 使用时&#xff0c;首先通过解构赋值获取状态值和更新函数&#xff0c;并设置初…...

Nacos config 配置中心详解

目录 一、Nacos启动 1.预备环境准备 2.下载编译后压缩包方式 3. 启动服务器 二、 配置管理 多配置格式编辑器 编辑DIFF 示例代码 监听者查询 配置的版本及一键回滚 命名空间管理 登录管理 修改默认用户名/密码方法 关闭登录功能 会话时间 三、启动配置管理 添加依赖&#xff1a…...

C++ UDP通信

#pragma once #ifndef XUDP_H #define XUDP_H#ifdef WIN32 #include <windows.h> #define socklen_t int #else #include <arpa/inet.h> #define closesocket close //替换close函数 #include <unistd.h>#include<iostream> #endifclass XUdp { pub…...

自由程序员想接私活?那你还不得知道这几个接单平台!最后一个就是宝藏!!

相信喜欢搞钱的程序员都知道&#xff0c;平常在平台上接点私活&#xff0c;利用闲暇时间接单是搞钱的常用套路&#xff0c;可是你确定你选对平台了吗&#xff1f;不管你是刚准备接单的小白&#xff0c;还是已经干了一段时间的老油条&#xff0c;都建议你看完本期文章&#xff0…...

二叉树与递归的相爱相杀

数据结构之二叉树 一、基于二叉树的基础操作1.二叉树的构建2.二叉树的遍历①前序遍历&#xff08;深度遍历&#xff09;②中序遍历③后序遍历④层序遍历判断一棵二叉树是否是完全二叉树&#xff08;基于层序遍历的思想&#xff09; 3.二叉树的数量问题①求二叉树结点个数②求二…...

Docker 安装 reids

docker run -itd --name myredis -p 6379:6379 redis --requirepass “123456” --restartalways --appendonly yes...

opensl学习——base16编码解码、base64编码解码、ASCII码表、扩展ASCII码

文章目录 ASCII表概述base家族简单说明 Hex(十六进制)编码、Base32编码、Base64编码、base256编码base16编码与解码base64编码概述转换过程不足 3 字节处理方法例子一,不足3字节&#xff0c;只有一个字节例子二,不足3字节&#xff0c;只有两个字节 base64示例代码1代码分析 acl…...

gazebo各种插件

类别 libgazebo_ros_api_plugin.so&#xff1a;提供与Gazebo仿真环境进行通信的API接口。 libgazebo_ros_block_laser.so&#xff1a;模拟激光传感器的插件。 libgazebo_ros_bumper.so&#xff1a;模拟碰撞传感器的插件。 libgazebo_ros_camera.so&#xff1a;模拟相机传感器的…...

C语言Free空指针会怎样?

在C语言中&#xff0c;使用free函数释放一个空指针是安全的&#xff0c;不会引发任何错误或异常。具体来说&#xff0c;当使用free函数释放一个空指针时&#xff0c;free函数会忽略这个空指针&#xff0c;并且不会执行任何操作。这是因为free函数只对有效的指针进行内存释放操作…...

软件测试全套教程,软件测试自学线路图

软件测试&#xff1a; 软件测试是为了发现程序中的错误而执行程序的过程。 通俗的说&#xff0c;软件测试需要在发布软件之前&#xff0c;尽可能的找软件的错误&#xff0c;尽量避免在发布之后给用户带来不好的体验&#xff0c;并要满足用户使用的需求。 现在市面上这么多软…...

禁止浏览器缩放

禁止浏览器缩放 1. 页面中添加如下代码&#xff1a;2. css单位统一使用rem&#xff0c;如下&#xff1a; 两个条件即可实现&#xff1a; 1. 动态修改html属性fontsize的值&#xff1b; 2. css单位统一使用rem。 1. 页面中添加如下代码&#xff1a; // 定义基准字体 new functi…...

前端食堂技术周刊第 100 期:TS 5.3 Beta、ViteConf2023、Rspress 1.0、Fresh 1.5、Chrome 118

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;乌龙金桂 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…...

汇川IT7000系列HMI使用脚本实现画面跳转时自动切换手自动模式

汇川IT7070E工业HMI使用实例(1) 用脚本切换模式 我们在使用工业HMI做画面时,可能会有这样的需求,希望切换画面时,可以根据不同的画面,自动切换相应的模式,比如有些画面是进行手动操作的,有些画面是进行自动操作的,当我们需要手动时,希望进入画面自动切换为“手动模…...

FDTD Solutions笔记

FDTD Solutions笔记 目录使用流程实例 目录 使用流程 实例 材料条件 步骤 基底 2. 添加规则膜层 3. 添加仿真区 解释&#xff1a; 仿真区为&#xff08;0,0&#xff09;&#xff0c;x方向为0.4&#xff0c;y方向是1 解释&#xff1a; 一般先用低精度进行计算 解释&#xff1a…...

SQL SELECT DISTINCT(选择不同) 语法

SQL SELECT DISTINCT 语法 SELECT DISTINCT语法用于仅返回不同的&#xff08;different&#xff09;值。 在一张表内&#xff0c;一列通常包含许多重复的值; 有时你只想列出不同的&#xff08;different&#xff09;值。 SELECT DISTINCT语句用于仅返回不同的&#xff08;diffe…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...