【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);}}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31204.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

豆瓣电影top250网页爬虫

设计思路 选择技术栈:确定使用Python及其相关库&#xff0c;如requests用于发送网络请求&#xff0c;获取网址&#xff0c;用re(正则表达式)或BeautifulSoup用于页面内容解析。设计流程:规划爬虫的基本流程&#xff0c;包括发起请求、接受响应、解析内容、存储数据等环节。模块…

小程序中用font-spider压缩字体后,字体没效果(解决办法)

因为项目中需要引入外部字体&#xff0c;有两种方案&#xff0c; 第一是把字体下载到本地&#xff0c; 第二种是cdn请求服务器放字体的地址 但是小程序是有大小限制的&#xff0c;所以必须要压缩字体大小&#xff0c;这时候有些人就说了&#xff0c;那把字体放在服务器上&a…

【人工智能】—基于K-Means算法商场顾客聚类实战教程

在这篇博文之前一直是给大家做机器学习有监督学习教程&#xff0c;今天来一篇无监督学习教程。 K-Means算法是一种基于中心的聚类方法&#xff0c;它试图找到数据点的K个簇&#xff0c;使得簇内的数据点尽可能相似&#xff0c;而簇间的数据点尽可能不同。下面是K-Means算法的详…

Spring Boot集成tablesaw插件快速入门

1 什么是tablesaw&#xff1f; Tablesaw是一款Java的数据可视化库&#xff0c;主要包括两部分&#xff1a; 数据解析库&#xff0c;主要用于加载数据&#xff0c;对数据进行操作(转化&#xff0c;过滤&#xff0c;汇总等)&#xff0c;类比Python中的Pandas库&#xff1b; 数据…

苹果cms10影视网整站源码下载/苹果cms模板MXone Pro自适应影视电影网站模板

下载地址&#xff1a;苹果cms10影视网整站源码下载/苹果cms模板MXone Pro自适应影视电影网站模板 模板带有夜间模式、白天晚上自动切换&#xff0c;有观影记录、后台设置页。全新UI全新框架&#xff0c;加载响应速度更快&#xff0c;seo更好&#xff0c;去除多余页面优化代码。…

从零开始搭建创业公司全新技术栈解决方案

从零开始搭建创业公司全新技术栈解决方案 关于猫头虎 大家好&#xff0c;我是猫头虎&#xff0c;别名猫头虎博主&#xff0c;擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程、bug解决思路、开发工具教程、前沿科技资讯、产品评测图文、产品使用体…

Ollma本地大模型沉浸式翻译【403报错解决】

最终效果 通过Chrome的 沉浸式翻译 插件&#xff0c;用OpenAI通用接口调用本地的Ollma上的模型&#xff0c;实现本地的大模型翻译文献。 官方文档指导的Ollama的配置&#xff1a;一定要配置环境变量&#xff0c;否则会出现【403报错】

GoogLeNet(InceptionV3)模型算法

GoogLeNet 团队在给出了一些通用的网络设计准则&#xff0c;以期望在不提高网络参数 量的前提下提升网络的表达能力&#xff1a; 避免特征图 (feature map) 表达瓶颈&#xff1a;从理论上讲&#xff0c;尺寸 (seize) 才包含了相关结构等重要因素&#xff0c;维度(channel) 仅仅…

torch.optim 之 Algorithms (Implementation: for-loop, foreach, fused)

torch.optim的官方文档 官方文档中文版 一、Implementation torch.optim的官方文档在介绍一些optimizer Algorithms时提及它们的implementation共有如下三个类别&#xff1a;for-loop, foreach (multi-tensor), and fused。 Chat-GPT对这三个implementation的解释是&#xf…

账号和权限的管理

文章目录 管理用户账号和组账号用户账号的分类超级用户普通用户程序用户 UID&#xff08;用户id)和(组账号)GIDUID用户识别号GID组标识号 用户账号文件添加用户账号设置/更改用户口令 管理用户账号和组账号 用户账号的分类 超级用户 root 用户是 Linux 操作系统中默认的超级…

第N5周:调用Gensim库训练Word2Vec模型

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 目录 本周任务: 1.安装Gensim库 2.对原始语料分词 3.停用词 4.训练Woed2Vec模型 …

办展览如何盈利?论办展的商业模式

想要弄清楚办展览怎么赚钱这个问题&#xff0c;我可以来说说。 首先来说说展览收益的大头&#xff1a;门票收入。 这个其实是可以大致预测的。简单来说&#xff0c;就是用流量乘以到店率。 但别忘了&#xff0c;这背后得有合适的展览定位、方便的展览场地和合理的票价。 说…

小林图解系统-三、操作系统结构

Linux 内核 vs Windows 内核 内核 作为应用连接硬件设备的桥梁&#xff0c;保证应用程序只需要关心与内核交互&#xff0c;不需要关心硬件的细节 内核具备四个基本能力&#xff1a; 管理进程、线程&#xff0c;决定哪个进程、线程使用CPU&#xff0c;也就是进程调度的能力&a…

Linux——ansible关于“文件操作”的模块

修改文件并将其复制到主机 一、确保受管主机上存在文件 使用 file 模块处理受管主机上的文件。其工作方式与 touch 命令类似&#xff0c;如果不存在则创建一个空文件&#xff0c;如果存在&#xff0c;则更新其修改时间。在本例中&#xff0c;除了处理文件之外&#xff0c;Ansi…

华为设备SSH远程访问配置实验简述

一、实验需求: 1、AR1模拟电脑SSH 访问AR2路由器。 二、实验步骤&#xff1a; 1、AR1和AR2接口配置IP&#xff0c;实现链路通信。 2、AR2配置AAA模式 配置用户及密码 配置用户访问级别 配置用户SSH 访问服务 AR2配置远程服务数量 配置用户远程访问模式为AAA 配置允许登录接入用…

【问题记录】Ubuntu提示: “E: 软件包 gcc 没有可安装候选“

Ubuntu提示: "E: 软件包 gcc 没有可安装候选" 一&#xff0c;问题现象二&#xff0c;问题原因&解决方法 一&#xff0c;问题现象 在虚拟机Ubuntu中进行安装gcc命令时报错&#xff1a;“E: 软件包 gcc 没有可安装候选”: 二&#xff0c;问题原因&解决方法 …

性能测试-性能监控分析与调优(三)《实战》

性能监控 使用命令监控 cpu瓶颈分析 top命令 在进行性能测试时使用top命令&#xff0c;界面如下 上图可以看出 CPU 概况区&#xff1a; %Cpu(s): us&#xff08;用户进程占用CPU的百分比&#xff09;, 和 sy&#xff08;系统进程占用CPU的百分比&#xff09; 的数值很高…

代码随想录-Day36

452. 用最少数量的箭引爆气球 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂…

Linux--视频推流及问题

方案一&#xff1a; mjpg-streamer,它运行在ARM板上 在手机上使用浏览器直接观看视频 方案二&#xff1a; 推流端&#xff08;Fmpeg&#xff09;--rtmp-->Nginx&#xff08;流媒体服务器&#xff09;--rtmp/httpflv/hls-->浏览器、播放器 此篇文章记录方案二的具体细…

Meta悄咪咪的发布多款AI新模型

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…