【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.rhkb.cn/news/354960.html

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

相关文章

【数据结构】红黑树实现详解

在本篇博客中&#xff0c;作者将会带领你使用C来实现一棵红黑树&#xff0c;此红黑树的实现是基于二叉搜索树和AVLTree一块来讲的&#xff0c;所以在看本篇博客之前&#xff0c;你可以先看看下面这两篇博客 【C】二叉搜索树-CSDN博客 【数据结构】AVLTree实现详解-CSDN博客 在这…

使用opencv合并两个图像

本节的目的 linear blending&#xff08;线性混合&#xff09;使用**addWeighted()**来添加两个图像 原理 (其实我也没太懂&#xff0c;留个坑&#xff0c;感觉本科的时候线代没学好。不对&#xff0c;我本科就没学线代。) 源码分析 源码链接 #include "opencv2/imgc…

spark学习总结

系列文章目录 第1天总结&#xff1a;spark基础学习 1- Spark基本介绍&#xff08;了解&#xff09;2- Spark入门案例&#xff08;掌握&#xff09;3- 常见面试题&#xff08;掌握&#xff09; 文章目录 系列文章目录前言一、Spark基本介绍1、Spark是什么1.1 定义1.2 Spark与M…

从0进入微服务需要了解的基础知识

文章目录 系统架构演化过程为什么要了解系统架构的演化过程技术发展认知技术选型与创新 演变过程单体架构分层-分布式集群微服务 分布式\集群\微服务 微服务中的核心要素-拆分原则项目拆分与复杂度微服务的拆分维度有哪些小结 微服务中的核心要素服务化进行拆分后一定是微服务&…

Unity和UE免费领恐怖书本头怪兽角色模型恐怖或奇幻游戏monster适合FPS类型PBR202406202143

Unity和UE免费领恐怖书本头怪兽角色模型恐怖或奇幻游戏monster适合FPS类型PBR202406202143 Unity恐怖书本头怪兽角色模型&#xff1a;https://prf.hn/l/zpBqgVl UE恐怖书本头怪兽角色模型&#xff1a;https://prf.hn/l/4PzY1Qy 作者其他资产&#xff1a;https://prf.hn/l/0…

百万级 QPS 接入层网关架构方案演进

文章目录 前言1、单机架构2、DNS 轮询3、Nginx 单机4、Nginx 主备 Keepalived5、LVS 主备 Keepalived Nginx 集群6、LVS 主备 Keepalived Nginx 集群 DNS 轮询 前言 随着PC、移动互联网的快速发展&#xff0c;越来越多的人通过手机、电脑、平板等设备访问各种各样APP、网…

OCC介绍及框架分析

1.OCC介绍 Open CASCADE &#xff08;简称OCC&#xff09;是一开源的几何造型引擎&#xff0c;OCCT库是由Open CASCADE公司开发和市场运作的。它是为开源社区比较成熟的基于BREP结构的建模引擎&#xff0c;能够满足二维三维实体造型和曲面造型&#xff0c;国内研究和使用它的单…

[论文笔记]Are Large Language Models All You Need for Task-Oriented Dialogue?

引言 今天带来论文Are Large Language Models All You Need for Task-Oriented Dialogue?的笔记。 主要评估了LLM在完成多轮对话任务以及同外部数据库进行交互的能力。在明确的信念状态跟踪方面&#xff0c;LLMs的表现不及专门的任务特定模型。然而&#xff0c;如果为它们提…

【机器学习】基于稀疏识别方法的洛伦兹混沌系统预测

1. 引言 1.1. DNN模型的来由 从数据中识别非线性动态学意味着什么&#xff1f; 假设我们有时间序列数据&#xff0c;这些数据来自一个&#xff08;非线性&#xff09;动态学系统。 识别一个系统意味着基于数据推断该系统的控制方程。换句话说&#xff0c;就是找到动态系统方…

[创业之路-120] :全程图解:软件研发人员如何从企业的顶层看软件产品研发?

目录 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼 一、企业全局 二、供应链 三、团队管理 四、研发流程IPD 五、软件开发流程 六、项目管理 七、研发管理者的自我修炼

系统架构设计师 - 数据库系统(1)

数据库系统 数据库系统数据库模式 ★分布式数据库 ★★★数据库设计阶段 ★★ER模型 ★关系模型 ★ ★结构约束条件完整性约束 关系代数 ★ ★ ★ ★概述自然连接 大家好呀&#xff01;我是小笙&#xff0c;本章我主要分享系统架构设计师 - 数据库系统(1)知识&#xff0c;希望内…

idea插件开发之在项目右键添加菜单

写在前面 本文看下如何在右键列表中增加菜单。 正戏 首先创建一个Action&#xff0c;要显示的menu选择ProjectViewPopupMenu&#xff0c;如下&#xff1a; action public class CAction extends AnAction {Overridepublic void actionPerformed(AnActionEvent e) { // …

OSPF 动态路由协议(思科、华为)

#交换设备 OSPF 动态路由协议 一、基本概念 1.中文翻译&#xff1a;开放式最短路径优先路由协议&#xff08;open shortest path first&#xff09;&#xff0c;是一个内部网关路由协议&#xff08;一个自治系统内&#xff09;2.也称为&#xff1a;链路状态路由协议&#xf…

火爆全网 LLM大模型教程:从零开始构建大语言模型,git突破18K标星

什么&#xff01;一本书的Github仓库居然有18.5k的星标&#xff01;&#xff08;这含金量不必多说&#xff09; 对GPT大模型感兴趣的有福了&#xff01;这本书的名字叫 《Build a Large Language Model (From Scratch)》 也就是 从零开始构建大语言模型&#xff01; 虽然这是一…

常说的云VR是什么意思?与传统vr的区别

虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;是一种利用计算机技术模拟产生一个三维空间的虚拟世界&#xff0c;让用户通过视觉、听觉、触觉等感官&#xff0c;获得与现实世界类似或超越的体验。VR技术发展历程可追溯至上世纪&#xff0c;经历概念提出、…

鸿蒙 Web组件的生命周期(api10、11、12)

概述 开发者可以使用Web组件加载本地或者在线网页。 Web组件提供了丰富的组件生命周期回调接口&#xff0c;通过这些回调接口&#xff0c;开发者可以感知Web组件的生命周期状态变化&#xff0c;进行相关的业务处理。 Web组件的状态主要包括&#xff1a;Controller绑定到Web组…

两行css 实现瀑布流

html <ul ><li><a href"" ><img src"05094532gc6w.jpg" alt"111" /><p>传奇</p></a></li><li><a href"" ><img src"05094532gc6w.jpg" alt"111"…

文件防篡改监控工具 - WGCLOUD全面介绍

WGCLOUD是一款优秀的运维监控软件&#xff0c;免费、轻量、高效&#xff0c;部署容易&#xff0c;上手简单&#xff0c;对新手非常友好 WGCLOUD部署完成后&#xff0c;点击菜单【文件防篡改】&#xff0c;可以看到如下页面 我们点击【添加】按钮&#xff0c;输入监控文件的信息…

深圳比创达EMC|EMC与EMI测试整改:保障电子设备电磁兼容性步骤

随着电子技术的迅猛发展&#xff0c;电子设备在我们的日常生活中扮演着越来越重要的角色。然而&#xff0c;这些设备在运行时产生的电磁干扰&#xff08;EMI&#xff09;以及对外界电磁干扰的敏感性&#xff08;EMC&#xff09;问题&#xff0c;不仅影响着设备本身的性能&#…

Windows电脑部署Jellyfin服务端并进行远程访问配置详细教程

文章目录 前言1. Jellyfin服务网站搭建1.1 Jellyfin下载和安装1.2 Jellyfin网页测试 2.本地网页发布2.1 cpolar的安装和注册2.2 Cpolar云端设置2.3 Cpolar本地设置 3.公网访问测试4. 结语 前言 本文主要分享如何使用Windows电脑本地部署Jellyfin影音服务并结合cpolar内网穿透工…