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

【Unity设计模式】状态编程模式

在这里插入图片描述


前言

最近在学习Unity游戏设计模式,看到两本比较适合入门的书,一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》

这两本书介绍了大部分会使用到的设计模式,因此很值得学习

本专栏暂时先记录一些教程部分,深入阅读后续再更新


文章目录

  • 前言
  • 有限状态机
  • 如何实现状态模式


有限状态机

在游戏中有一个可玩的角色,通常情况他是站在地面上的,当控制控制器,他就会进入行走状态,当按下跳跃,则他会跳到半空,下落后又会回到站立状态

在这里插入图片描述

如果你做过动画机Animator,你一定能理解这个状态切换是如何实现的。上述的状态图类似于流程图,但是有一些区别:

  • 它由多种状态构成,且每个时间点只有一个状态是处于活动的
  • 每个状态可以根据运行条件转换为其他状态
  • 当发生状态转换时,原来的状态由活动切换为不活动,而转换的目标状态变为活动

我们将上图这样的状态模型称为有限状态机FSM,有限状态机在角色AI,程序设计尤其操作系统中十分常见。

有限状态机由数量有限的状态构成,它有一个初始状态,并包含了其他状态以及转换状态的条件。状态机在任意时刻都处于其中的某一状态,并且在一定条件下会从一种状态切换为另一种状态,以响应转换的外部输入。

状态模式不仅可以用于管理角色,道具的状态,甚至可以用于管理整个游戏系统的状态。包括Mono Behavior的生命周期,实际上也可视作一种状态模式


如何实现状态模式

状态模式看起来似乎很简单,我们只需要让对象进行状态判断,根据状态来选择行为就行了。

那我是不是可以定义一个枚举类型来分出状态,然后让角色根据他们所处的状态在内部进行行为切换就行了呢?

public enum EnemyState
{Idle,Walk,Jump
}public class Enemy : MonoBehaviour
{private EnemyState state;private void Update(){GetInput();switch (state){case EnemyState.Idle:Idle();break;case EnemyState.Walk:Walk();break;case EnemyState.Jump:Jump();break;}}
}

看起来实现了状态模式,但显然这种实现是依托答辩。

首先,难道我们每定义一个角色,就需要在其内部管理它自身的状态?
齐次,如果我们每添加一个状态,就需要一个Switch Case,那代码会有多冗余?
最后,上述代码显然是高耦合的,如果我们需要添加或者删去某状态,那么所有使用了该状态的代码都需要被修改。

因此,用枚举类型实现状态显然不合适,记住设计模式的重要原则,对拓展开放,对修改关闭

因此同理,让所有角色继承一个状态基类,在基类中定义各种状态实现的方法,并在子类中重写状态实现的虚方法也是不行的,因为基类一旦改变子类也要改变。

所以,我们需要在不修改角色代码的情况下,既要实现状态的拓展和删除,又要方便我们对每个状态的角色事件进行定义。一个想法就是让状态持有角色并在状态中完成业务处理逻辑,而非角色根据状态来实现业务逻辑。

这个想法很像我之前学习的一个案例(也许是工厂模式),银行有很多业务,但是如果每增加一个业务就需要修改银行类的代码,显然违背了开闭原则,因此银行应当只负责返回给用户相应的业务,而具体的业务逻辑则需要业务类本身来执行。就方便对银行业务进行增减。

因此角色的状态事件则需要由状态类本身来进行定义,好处是减少了耦合,代码也会更加清晰。但坏处是我们可能要为每个角色类定义多个衍生出来的状态类,类的数量会爆炸式的增长(此时用命名空间和程序集来管理多个相关类的好处就凸显出来了)

Unity 状态模式(实例详解)

// 定义抽象状态类
public abstract class CharacterState
{protected Character character;public void SetCharacter(Character _character){this.character = _character;}// 抽象方法,子类需要实现具体行为public abstract void Update();
}// 具体状态类:IdleState
public class IdleState : CharacterState
{public override void Update(){Debug.Log("角色处于闲置状态");// 检查是否应该转换到其他状态,如按下移动键则切换至MoveStateif (Input.GetKey(KeyCode.W)){character.ChangeState(new MoveState());}}
}// 具体状态类:MoveState
public class MoveState : CharacterState
{public override void Update(){Debug.Log("角色正在移动");// 检查是否应返回闲置状态或切换至其他状态if (!Input.GetKey(KeyCode.W)){character.ChangeState(new IdleState());}}
}------------------------------------------------------// 角色类持有当前状态并处理状态切换
public class Character : MonoBehaviour
{private CharacterState currentState;public void ChangeState(CharacterState newState){if (currentState != null){currentState.SetCharacter(null);}currentState = newState;currentState.SetCharacter(this);}void Update(){currentState.Update();}
}

在上述例子中,我们把状态的业务逻辑本身定义到了状态类中,并将对应的持有角色传入状态类,那么当角色进行状态改变时,则行为逻辑也就切换为对应状态类提供的Update方法。由状态类中对角色逻辑进行处理。

为了进一步解除角色类和状态类的耦合(角色未必需要有状态切换的需求),可以创建一个抽象的上下文类(Context),由它来持有当前状态并处理状态之间的切换:

管理StateSystem的文件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern.StateSystem
{/// <summary>/// 状态抽象基类/// </summary>public abstract class State{public abstract void Handle();}/// <summary>/// 状态生命周期抽象基类/// </summary>public abstract class StateBehaviour:State{// 状态持有者protected ContextBehaviour Context;// 几个用于状态生命周期调度的抽象方法public abstract void Update();public abstract void Enter();public abstract void Exit();}/// <summary>/// 管理状态的上下文基类/// </summary>public class Context{// 当前状态private State _state;public void SetState<T>(T state) where T:State{_state = state;}public State GetState(){return _state;}public void Requst(){_state?.Handle();}}/// <summary>/// 上下文管理状态生命周期基类/// </summary>public class ContextBehaviour : Context{// 当前持有状态private StateBehaviour _stateBehaviour;// 覆盖父类的获取状态方法public new void SetState<T>(T state) where T:StateBehaviour{_stateBehaviour = state;}public new StateBehaviour GetState(){return _stateBehaviour;}// 几个用于状态生命周期调度的虚方法public virtual void ChangeState(StateBehaviour stateBehaviour){_stateBehaviour.Exit();SetState(stateBehaviour);_stateBehaviour.Enter();}public virtual void Update(){_stateBehaviour.Update();}public virtual void NotifyStateEnter(){_stateBehaviour.Enter();}public virtual void NotifyStateExit(){_stateBehaviour.Exit();}}}

角色基类定义代码:

using StatePattern.StateSystem;
using System;
using UnityEngine;
using UnityEngine.UI;namespace CharacterClass
{#region 基类定义/// <summary>/// 角色状态基类/// </summary>public abstract class CharacterState : StateBehaviour { }/// <summary>/// 角色状态上下文基类/// </summary>public class CharacterContext : ContextBehaviour { }/// <summary>/// 角色基类/// </summary>public class Character : MonoBehaviour{private CharacterContext _context;public CharacterContext Context => _context;public Button StateChangeBtn;private void Start(){var riginState = new IdleState();_context = new CharacterContext();_context.SetState(riginState);var newState = new MoveState();StateChangeBtn.onClick.AddListener(() => { ChangeState(newState); });}private void Update(){_context.Update();}public void ChangeState(CharacterState characterState){_context.ChangeState(characterState);}}#endregion/// <summary>/// 角色状态类IdleState/// </summary>public class IdleState : CharacterState{public override void Update(){Debug.Log("处于IdleState");}public override void Enter(){Debug.Log("进入IdleState");}public override void Exit(){Debug.Log("退出IdleState");}public override void Handle(){Debug.Log("IdleState下执行事件");}}/// <summary>/// 角色状态类MoveState/// </summary>public class MoveState : CharacterState{public override void Update(){Debug.Log("处于MoveState");}public override void Enter(){Debug.Log("进入MoveState");}public override void Exit(){Debug.Log("退出MoveState");}public override void Handle(){Debug.Log("MoveState下执行事件");}}}

我们把Character脚本挂载,然后传入Button用于手动切换状态

在这里插入图片描述

这样我们就实现状态模式了。上面的代码写的实在太漂亮了,我都忍不住想夸我自己

我们还有更丧心病狂的想法,如果我们需要管理的状态不是单个,而是一系列的状态,那么我们可能就需要维护一个状态队列或者状态栈,此时一个状态切换上下文已经不够我们用了,我们需要一个状态机!

	public class NullState : StateBehaviour{public override void Handle(){throw new System.NotImplementedException();}public override void Update(){throw new System.NotImplementedException();}public override void Enter(){throw new System.NotImplementedException();}public override void Exit(){throw new System.NotImplementedException();}}public class StateMachine{private ContextBehaviour _contextBehaviour;public ContextBehaviour ContextBehaviour => _contextBehaviour;private NullState _nullState = new NullState();private StateBehaviour _prevState= new NullState();public StateMachine (ContextBehaviour contextBehaviour){_contextBehaviour = contextBehaviour;}public StateMachine (ContextBehaviour contextBehaviour,StateBehaviour riginState){_contextBehaviour = contextBehaviour;_contextBehaviour.SetState(riginState);}private Queue<StateBehaviour> _stateQueue = new Queue<StateBehaviour>();public void StateEnQueue(StateBehaviour stateBehaviour){_stateQueue.Enqueue(stateBehaviour);}public StateBehaviour StateDeQueue(){if (_stateQueue.Count > 0){return _stateQueue.Dequeue();}else{return _nullState;}}public void Update(){_contextBehaviour.Update();}public void NextState(){_prevState = _contextBehaviour.GetState();_contextBehaviour.ChangeState(StateDeQueue());}public void PrevState(){_contextBehaviour.ChangeState(_prevState);}}

这样我们就不是让角色持有上下文,而是让角色持有状态机本身。

在某些需要的时候更新状态机就可以处理一系列状态。我们就可以对状态进行各种操作,例如回到上一个状态,例如在一个事件中根据我们的需要传入一系列状态,并按照我们的想法对状态机中的状态进行触发。甚至多个角色持有同个状态机,一个状态机持有多个状态的上下文等等奇思妙想。

相关文章:

【Unity设计模式】状态编程模式

前言 最近在学习Unity游戏设计模式&#xff0c;看到两本比较适合入门的书&#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式&#xff0c;因此很值得学习 本…...

圆的面积并三角形面积并

三角形面积并 #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; const int maxn 110; #define x first #define y second typedef pair<double, double> PDD; const d…...

Spring Data JPA介绍与CRUD实战演练

文章目录 一、Spring Data JPA 简介二、Spring Data JPA 与 MyBatis Plus 比较设计哲学和抽象层次SQL 控制学习曲线和技术要求性能与优化综合考虑 三、SpringDataJpa实战演练1. 创建user表2. 搭建Spring Boot开发环境3. pom.xml文件内容4. application.yml文件内容5. Applicati…...

Python网络爬虫实战6—下一页,模拟用户点击,切换窗口

【前期提要】感兴趣的可以看看往期文章哈~ Python网络爬虫5-实战网页爬取 Python网络爬虫4-实战爬取pdf Pyhon网络爬虫3-模拟用户点击 Python网络爬虫实战2-下载url下的pdf Python网络爬虫基础1 1.需求背景 针对长虹美菱电器说明书网页形式&#xff0c;编写爬虫代码&#xff…...

Notepad++插件 Hex-Edit

Nptepad有个Hex文件查看器&#xff0c;苦于每次打开文件需要手动开插件显示Hex&#xff0c;配置一下插件便可实现打开即调用 关联多个二进制文件&#xff0c;一打开就使用插件的方法&#xff0c;原来是使用空格分割&#xff01;&#xff01;&#xff01;...

Matlab要这样批量读取txt数据!科研效率UpUp第10期

假如我们有多组txt格式的数据&#xff1a; 其数据格式是这样的&#xff1a; 想要批量读取这些数据&#xff0c;并把他们画在一张图上&#xff0c;该怎么操作呢&#xff1f; ​之前有分享load函数的版本&#xff0c;本期进一步分享适用性更强的readtable函数的实现方法​。 首…...

buuctf----firmware

- -一定不能再ubutu22进行,我是在18(血泪教训) binwalk安装 buuctf firmware(binwalk和firmware-mod-kit的使用)_buu firmware-CSDN博客 参考博客 指令 sudo apt-get update sudo apt-get install python3-dev python3-setuptools python3-pip zlib1g-dev libmagic-dev pi…...

ssl证书90天过期?保姆级教程——使用acme.sh实现证书的自动续期

腾讯云相关文档相关参考-有的点不准确 前言 最近https到期了&#xff0c;想着手动更新一下https证书&#xff0c;结果发现证书现在的有效期只有90天&#xff0c;于是想找到一个自动更新证书的工具&#xff0c;发现了acme.sh&#xff0c;但是网上的文章质量参差不齐&#xff0…...

由于bug造成truncate table卡住问题

客户反应truncate table卡主&#xff0c;检查awr发现多个truncate在awr报告期内一直没执行完&#xff0c;如下&#xff1a; 检查ash&#xff0c;truncate table表的等待事件都是“enq: RO - fast object reuse”和“local write wait” 查找“enq: RO - fast object reuse”&am…...

Charles抓包工具系列文章(二)-- Repeat 回放http请求

一、什么是http请求回放 当我们对客户端进行抓包&#xff0c;经常会想要重试http请求&#xff0c;或者改写原有部分进行重新请求&#xff0c;都需要用到回放http请求。 还有一种场景是压力测试&#xff0c;对一个请求进行重复请求多少次&#xff0c;并加上适当的并发度。 这里…...

jemeter基本使用

后端关验签&#xff0c;设置请求头编码和token 配置编码和token...

【Golang】Steam 创意工坊 Mod 文件夹批量重命名

本文将介绍一个使用Go语言编写的脚本&#xff0c;其主要功能是解析XML文件并基于解析结果重命名文件夹。这个脚本适用于需要对文件夹进行批量重命名&#xff0c;并且重命名规则依赖于XML文件内容的情况。 脚本功能概述 Steam创意工坊下载的Mod文件夹批量重命名为id名称 运行前…...

求职刷题力扣DAY33--贪心算法part04

DAY 33 贪心算法part04 1. 452. 用最少数量的箭引爆气球 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可…...

aws的eks(k8s)ingress+elb部署实践

eks&#xff08;k8s&#xff09;版本1.29 ingress 版本1.10.0 负载均衡elb 1. 创建Ingress-Nginx服务 部署项目地址【点我跳转】推荐自定义部署 可绑定acm证书什么的自己属性 这里就是aws上面Certificate Manager产品上面创建证书 导入 创建都行 对应集群版本推荐阵列GitH…...

大数据面试题之YARN

目录 1、介绍下YARN 2、YARN有几个模块 3、YARN工作机制 4、YARN有什么优势&#xff0c;能解决什么问题? 5、YARN容错机制 6、YARN高可用 7、YARN调度器 8、YARN中Container是如何启动的? 9、YARN的改进之处&#xff0c;Hadoop3.x相对于Hadoop 2.x? 10、YARN监控 1…...

最小生成树模板(prim,heap-prim,kruskal)

prim 出圈法&#xff0c;时间复杂度 O ( n 2 ) O(n^2) O(n2) #include<iostream> #include<vector> using namespace std; #define MAX_N 5000 #define inf 100000000 struct edge{int v,w; }; vector<edge>e[MAX_N5]; int d[MAX_N5],vis[MAX_N5]; int n,m…...

Centos 7 或 8配置国内yum源及epel源-1

官方教程 Yum工具详解 清理Yum缓存:[rootqfedu.com ~]# yum clean all缓存软件包信息: 提高搜索/安装软件的速度[rootqfedu.com ~]# yum makecache查询yum源信息: [rootqfedu.com ~]# yum repolist 查找软件:[rootqfedu.com ~]# yum search mysql 此命令会搜索到系…...

轻松解决Android复杂数据结构序列化

问题描述 当我编写quickupload库时&#xff0c;因为需要在 Service中进行上传任务&#xff0c;向Service传递时我发现需要传递的数据很多并且结构复杂&#xff0c;如果处理不好就会导致以下几个问题 耗时: 需要更多时间进行开发和测试以确保正确的数据处理。容易出错: 由于手…...

解析PDF文件中的图片为文本

解析PDF文件中的图片为文本 1 介绍 解析PDF文件中的图片&#xff0c;由两种思路&#xff0c;一种是自己读取PDF文件中的图片&#xff0c;然后用OCR解析&#xff0c;例如&#xff1a;使用PyMuPDF读取pdf文件&#xff0c;再用PaddleOCR或者Tesseract-OCR识别文字。另一种使用第…...

微信小程序表单

在我们的课程中&#xff0c;我们深入探讨了微信小程序表单的开发和应用。以下是我们课程的主要内容和收获&#xff1a; 一、课程目标 本课程旨在帮助学生掌握微信小程序表单的基本概念、开发流程和最佳实践。学生将学习如何创建和配置表单组件&#xff0c;处理表单数据&#xf…...

linux之kylin系统nginx的安装

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

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

ip子接口配置及删除

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