【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("角色处于闲置状态");
        // 检查是否应该转换到其他状态,如按下移动键则切换至MoveState
        if (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.mfbz.cn/a/727427.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过git命令查询某个用户提交信息

要查询某个用户通过 Git 提交了多少行代码&#xff0c;可以使用以下步骤和命令来实现。这些命令将统计该用户的添加和删除的代码行数。 1、切换到你的 Git 仓库&#xff1a; cd /path/to/your/repositorygit命令结果&#xff1a; 2、查询所有用户&#xff1a; git log --pr…

原子性(juc编程)

原子性 概述&#xff1a;所谓的原子性是指在一次操作或者多次操作中&#xff0c;要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断&#xff0c;要么所有的操作都不执行&#xff0c;多个操作是一个不可以分割的整体。 //比如说&#xff1a;你喂你女朋友吃冰淇…

SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion

SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion 文章目录 SOFTS: Efficient Multivariate Time Series Forecasting with Series-Core Fusion1. 论文背景1.1 通道独立-通道依赖的区别1.2 论文贡献 2. 模型架构2.1 SOFTS的主要架构2.2 STAR 模…

《QT实用小工具·七十一》基于Qt+Qml开发的文件传输工具

1、概述 源码放在文章末尾 该项目基于QTQML实现了文件传输的功能&#xff0c;可以在局域网环境下使用(热点)&#xff0c;扫描使用UDP&#xff0c;传输使用TCP&#xff0c;每一个文件传输使用独立的线程进行处理&#xff0c;高效便捷。 开发环境 使用Qt/Qml开发 QT版本&#x…

搭建开发模式下的以太坊私有链【Geth:1.14.5】

一、为什么用到私有链&#xff1f; 在以太坊的公有链上部署智能合约、发起交易需要花费以太币。而通过修改配置&#xff0c;可以在本机搭建一套以太坊私有链&#xff0c;因为与公有链没关系&#xff0c;既不用同步公有链庞大的数据&#xff0c;也不用花钱购买以太币&#xff0…

Mybatis中BaseEntity作用

新建各种对象的时候&#xff0c;一般来说&#xff0c;有几个属性是所有对象共有的&#xff0c;比如说id,is_del&#xff0c;is_enable这些&#xff0c;然后设置一个基础对象&#xff0c;以后新建所有对象的时候都继承它&#xff0c;就省的每次都要写这些共有的属性了

生育是家庭和个人的重要的选择

生育是个人和家庭的重要选择&#xff0c;而国家对于生育政策的制定应该综合考虑多种因素&#xff0c;包括人口结构、经济发展和社会稳定等。同时&#xff0c;我们也应该认识到&#xff0c;男女不应该成为决定一个人是否能够生育的因素。男女在生育中扮演着不同的角色&#xff0…

在Maven工程中手动配置并测试SpringBoot(巨详)

本篇博客承继自博客&#xff1a; 在IDEA 2024.1.3 (Community Edition)中创建Maven项目_idea2024.1.3如何创建maven项目-CSDN博客 配置POM文件 打开工程中的pom.xml文件&#xff0c;先向其中写入 <parent><groupId>org.springframework.boot</groupId><…

【总线】AXI4第三课时:握手机制

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

Java面向对象的三大特性之一——继承

目录 一、继承概念 二、为什么要继承 三、继承语法&#xff08;关键字extends&#xff09; 四、父类成员访问 1、子类中访问父类的成员变量 &#xff08;1&#xff09;子类和父类不存在同名的成员变量 &#xff08;2&#xff09;子类和父类中存在同名的成员变量 2、子类中访…

Transformer学习理解

1.前言 本文介绍当下人工智能领域的基石与核心结构模型——Transformer&#xff0c;为什么说它是基石&#xff0c;因为以ChatGPT为代表的聊 天机器人以及各种有望通向AGI&#xff08;通用人工智能&#xff09;的道路上均在采用的Transformer。 Transformer也是当下NLP任…

基于PCL实现多边形框选点云并进行裁剪(附C++源码)

文章目录 一.算法效果二.算法原理PNPoly算法直线相交性判断三.代码实现一.算法效果 通过在PCL可视化界面上绘制2D封闭多边形来提取位于该封闭多边形内部或者外部的 的点,算法效果如下: 图1多边形裁剪点云效果图 二.算法原理 PNPoly算法 2D多边形框选裁剪点云,实际上可以简…

SSRF学习,刷题

[HNCTF 2022 WEEK2]ez_ssrf 给了一个Apache2的界面&#xff0c;翻译一下 就是一个默认的界面,目录扫描 可以看到flag.php,肯定是不能直接访问得到的&#xff0c;还有index.php&#xff0c;访问这个 可以看到三个参数data,host,port 还有fsockopen() 函数是 PHP 中用于打开一个…

代码随想录训练营Day 64|卡码网98. 所有可达路径(深搜)

1.所有可达路径 98. 所有可达路径 | 代码随想录 代码&#xff1a; &#xff08;深搜&#xff09;邻接矩阵表示 #include <iostream> #include <vector> using namespace std; vector<int> path; vector<vector<int>> result; void dfs(const ve…

LeetCode 算法:两两交换链表中的节点 c++

原题链接&#x1f517;&#xff1a;两两交换链表中的节点 难度&#xff1a;中等⭐️⭐️ 题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交…

QT中利用动画弄一个侧边栏窗口,以及贴条效果

1、效果 2、关键代码 void Widget::on_sliderBtn_clicked() {m_sliderWidget->show();QPropertyAnimation* animation = new QPropertyAnimation(m

政策更新记录:敏感信息访问权限与API使用变更

我们将更新“健康数据共享”政策,简化“健康数据共享”申请流程,并与“健康类应用”政策保持一致。此外,我们将于今年晚些时候在 Play 管理中心推出一项新的声明,取代当前使用表单进行申请的方式。 公布日期:2024-04-03 Health Connect 政策要求及常见问题解答 初步认识对…

[AIGC] 使用Google的Guava库中的Lists工具类:常见用法详解

在Java程序设计中&#xff0c;集合是我们最常用的数据结构之一。为了方便我们操作集合&#xff0c;Google的Guava库提供了一个名为Lists的工具类&#xff0c;它封装了许多用于操作List对象的实用方法。在本文中&#xff0c;我们将详细介绍其常见的用法&#xff0c;以帮助您更好…

volatile关键字(juc编程)

volatile关键字 3.1 看程序说结果 分析如下程序&#xff0c;说出在控制台的输出结果。 Thread的子类 public class VolatileThread extends Thread {// 定义成员变量private boolean flag false ;public boolean isFlag() { return flag;}Overridepublic void run() {// 线…

钡铼BL101网关助力智慧城市路灯远程智能管控

在迈向智慧城市的征途中&#xff0c;基础设施的智能化改造是关键一环&#xff0c;而路灯作为城市脉络的照明灯塔&#xff0c;其智能化升级对于节能减排、提升城市管理效率具有重要意义。钡铼BL101网关&#xff0c;作为Modbus转MQTT的专业桥梁&#xff0c;正以其卓越的性能和广泛…