Unity 设计模式-观察者模式(Observer Pattern)详解

news2024/12/26 17:56:11

观察者模式

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。这种模式用于事件处理系统,当某个对象发生改变时,依赖该对象的其他对象能够及时响应。

在观察者模式中,主要有两个角色:

  1. 被观察者(Subject):状态发生变化的对象,它维护了一组观察者列表,并在状态发生变化时通知它们。
  2. 观察者(Observer):依赖被观察者的对象,它注册到被观察者上,并在被观察者状态变化时得到通知。

1、什么时候使用观察者模式

事件系统:需要实现基于事件驱动的系统,许多UI系统使用观察者模式来创建按钮点击,值变化等事件

数据同步:当某个对象的状态改变时,依赖它的其他对象需要同步更新。

系统中存在一对多的依赖关系:例如,一个对象的变化需要通知多个对象做出反应,典型的例子包括股票价格变化通知、新闻订阅系统等。

通知机制:在某些情况下,多个模块需要接收到某个变化的信息(如游戏事件、社交媒体通知等)。

2、使用观察者模式的好处

解耦合:观察者和被观察者之间的耦合度低。被观察者只知道观察者实现了某些接口,而无需了解它们的具体实现细节。
灵活性:可以动态添加或移除观察者,系统的扩展性好。
提高代码的可维护性:将状态的变化与相应的反应分开,使代码更易于维护和修改。
响应式更新:观察者模式允许系统中的多个对象自动响应某个对象的状态变化,无需显式调用每个依赖对象。

3、使用时的注意事项

性能问题:当观察者数量过多时,每次状态改变都需要通知所有观察者,这可能会引起性能问题。
避免循环依赖:如果观察者在更新过程中再次触发了被观察者的通知,可能会导致循环调用或死锁。
顺序问题:多个观察者对同一事件做出响应时,要注意观察者之间的顺序依赖,可能会导致某些观察者未按预期更新。
内存泄漏问题:要确保观察者可以正确地从被观察者中移除,以避免内存泄漏问题。

4、我现在用通俗的例子来给大家描述一下观察者模式

想象有一个人气明星,比如周杰伦~。杰伦有非常多的歌迷,这些歌迷对他的新专辑翘首以盼(话说距离上次伟作已经过去两年了……),都在关注着杰伦的专辑动向,一有点风吹草动大家就会沸腾。那么,观察杰伦的歌迷们就是【观察者】,被歌迷们观察的杰伦就是【被观察者】。

在观察者模式中,被观察者的状态发生改变时,就会向所有的观察者们发送通知,观察者们就可以根据这个通知做出各自相应的行为。类比到杰伦和歌迷上,就是当杰伦发新专辑时(简直天方夜谭!),他会在各种社交媒体、音乐软件上发布这个消息,而所有关注杰伦的歌迷在看到这一消息后,有的掏出钱包,有的奔走相告,有的因激动而变身狒狒:吼吼哇哇!

当然,观察者模式中还有一个很重要的概念:【主题】,我更习惯称呼它为【中间体】。中间体是观察者和被观察者之间的桥梁,就好像一个代理人,负责管理有哪些观察者正在观察自己代理的被观察者。每当被观察者状态改变发送消息时,消息首先到达中间体,再由中间体传递出去。在杰伦和歌迷的比喻中,中间体就好比是社交媒体、音乐平台。

在 Unity 中使用 观察者模式

为了演示如何在 Unity 中使用 观察者模式。

我们将实现一个示例:当玩家接触到一个触发器(Trigger)时,游戏会通知观察者更新,例如改变颜色、显示文本等。

这个示例将演示如何使用观察者模式管理多个对象对玩家触发事件做出反应。

1、定义观察者接口

首先,我们定义一个观察者接口,所有的观察者类都需要实现这个接口。

public interface IObserver
{
    void OnNotify();
}
2、定义被观察者类

然后我们定义一个被观察者类。在这个示例中,被观察者是一个触发器,当玩家接触触发器时,它会通知所有观察者。

using System.Collections.Generic;
using UnityEngine;
 
public class TriggerSubject : MonoBehaviour
{
    private List<IObserver> observers = new List<IObserver>();
 
    // 注册观察者
    public void RegisterObserver(IObserver observer)
    {
        observers.Add(observer);
    }
 
    // 移除观察者
    public void RemoveObserver(IObserver observer)
    {
        observers.Remove(observer);
    }
 
    // 通知所有观察者
    public void NotifyObservers()
    {
        foreach (IObserver observer in observers)
        {
            observer.OnNotify();
        }
    }
 
    // Unity 的触发器事件,当玩家接触触发器时调用
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            NotifyObservers();
        }
    }
}
3、实现观察者类

接下来我们创建几个不同的观察者类,每个观察者类会响应触发器的通知。在这个例子中,我们将创建两个观察者:

  • 一个会改变颜色
  • 一个会显示文本

3.1 观察者 1:改变颜色的观察者

using UnityEngine;
 
public class ColorObserver : MonoBehaviour, IObserver
{
    public Renderer objectRenderer;
 
    public void OnNotify()
    {
        // 随机改变对象的颜色
        objectRenderer.material.color = new Color(Random.value, Random.value, Random.value);
        Debug.Log("ColorObserver: Color changed!");
    }
}

3.2 观察者 2:显示文本的观察者

using UnityEngine;
using UnityEngine.UI;
 
public class TextObserver : MonoBehaviour, IObserver
{
    public Text messageText;
 
    public void OnNotify()
    {
        // 显示通知文本
        messageText.text = "Player triggered the event!";
        Debug.Log("TextObserver: Text updated!");
    }
}
4、在场景中使用观察者模式

现在我们在 Unity 场景中设置以下内容:

  1. 创建一个空的 GameObject,命名为 Trigger,并将 TriggerSubject 脚本附加到该对象上。同时,在该对象上添加一个 BoxCollider,并勾选 Is Trigger。
  2. 创建两个 3D 物体(如立方体或球体),并附加 ColorObserver 脚本到其中一个物体,记得将 objectRenderer 变量拖入到 Inspector 中。
  3. 创建一个 UI 文本,附加 TextObserver 脚本,并将 messageText 变量拖入 Inspector。
  4. 在游戏的 Start() 函数中,注册观察者。
using UnityEngine;
 
public class GameManager : MonoBehaviour
{
    public TriggerSubject triggerSubject;
    public ColorObserver colorObserver;
    public TextObserver textObserver;
 
    void Start()
    {
        // 注册观察者
        triggerSubject.RegisterObserver(colorObserver);
        triggerSubject.RegisterObserver(textObserver);
    }
 
    void OnDestroy()
    {
        // 在销毁时移除观察者,避免内存泄漏
        triggerSubject.RemoveObserver(colorObserver);
        triggerSubject.RemoveObserver(textObserver);
    }
}
5、运行示例

1.将 GameManager 脚本挂载到 Unity 场景中的一个空对象上。
2.在 GameManager 中的 Inspector 窗口,将 triggerSubject、colorObserver 和 textObserver 分别拖入相应的字段。
3.运行游戏,当玩家接触 Trigger 对象时,注册的观察者将会被通知:
   物体的颜色会发生变化。
   UI 文本会显示 "Player triggered the event!"。

6、示例分析
  • 触发器 (TriggerSubject) 是被观察者,当玩家接触它时,它会调用 NotifyObservers() 方法通知所有的观察者。
  • 观察者 (ColorObserver 和 TextObserver) 实现了 IObserver 接口,并在 OnNotify() 方法中定义了各自的行为。
  • 通过这种设计,任何新增的观察者只需要实现 IObserver 接口,并注册到 TriggerSubject 中,而不需要修改已有的代码。

通过这个示例,我们可以看到如何在 Unity 中运用 观察者模式,处理多个对象对同一事件的响应。这种模式在处理游戏事件、状态变化等场景中十分有用,尤其是在复杂的游戏系统中。

今天是2024年12月3日

重复一段毒鸡汤来勉励我和你

你的对手在看书

你的仇人在磨刀

你的闺蜜在减肥

隔壁的老王在练腰

而你在干嘛?

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

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

相关文章

【webApp之h5端实战】首页评分组件的原生实现

关于评分组件,我们经常在现代前端框架中用到,UI美观效果丰富,使用体验是非常不错的。现在自己动手使用原生js封装下评分组件,可以用在自己的项目中。 组件实现原理 点击的❤左侧包括自己都是高亮的样式,右侧都是灰色的样式,这样就能把组件的状态区分开了。右边再加上辅…

unity与android拓展

一.AndroidStudio打包 1.通过Unity导出Android Studio能够打开的工程 步骤 1.设置导出基本信息&#xff1a;公司名、游戏名、图标、包名等关键信息 2.在File——>Build Settings中&#xff0c;勾选 Export Project 选项 3.点击Export 导出按钮 2.在Android Studio中打开Un…

几种常见的javascript设计模式

摘要 最近开发HarmonyOSApp&#xff0c;使用的Arkts语言&#xff0c;此语言类似后端C#语言风格&#xff0c;同时兼顾写后端接口的我突然想总结一下近8年前端开发中无意中使用的设计模式&#xff0c;我们用到了却不知属于哪些&#xff0c;下面和大家分享一下。 什么是前端设计…

2.4特征预处理(机器学习)

2.4特征预处理 2.4.1 什么是特征预处理 通过 一些转换函数将特征数据转换成更加适合算法模型的特征数据过程。 1 包含内容 数值型数据的无量纲化&#xff1a; 归一化 标准化 2 特征预处理API sklearn.preprocessing 为什么要进行归一化/标准化&#xff1f; 特征的单…

学习笔记052——Spring Boot 自定义 Starter

文章目录 Spring Boot 自定义 Starter1、自定义一个要装载的项目2、创建属性读取类 ServiceProperties3、创建 Service4、创建自动配置类 AutoConfigration5、创建 spring 工程文件6、将项目打成 jar 包7、jar 打包到本地仓库8、配置application.yml Spring Boot 自定义 Starte…

重学设计模式-建造者模式

本文介绍一下建造者模式&#xff0c;相对于工厂模式来说&#xff0c;建造者模式更为简单&#xff0c;且用的更少 定义 建造者模式是一种创建型设计模式&#xff0c;它使用多个简单的对象一步一步构建成一个复杂的对象。这种模式的主要目的是将一个复杂对象的构建过程与其表示…

复现SMPLify-X: Ubuntu22.04, Cuda-11.3, GPU=3090Ti

Env: 3090Ti CUDA 最低支持版本需要>cuda-11.1 Ubuntu 22.04 Installation: Installing CUDA11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run …

Milvus×OPPO:如何构建更懂你的大模型助手

01. 背景 AI业务快速增长下传统关系型数据库无法满足需求。 2024年恰逢OPPO品牌20周年&#xff0c;OPPO也宣布正式进入AI手机的时代。超千万用户开始通过例如通话摘要、新小布助手、小布照相馆等搭载在OPPO手机上的应用体验AI能力。 与传统的应用不同的是&#xff0c;在AI驱动的…

JAVA |日常开发中读写XML详解

JAVA &#xff5c;日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM&#xff08;Document Object Model&#xff09;方式读取 XML2.2 使用 SAX&#xff08;Simple API for XML&#xff09;方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…

GEOBench-VLM:专为地理空间任务设计的视觉-语言模型基准测试数据集

2024-11-29 ,由穆罕默德本扎耶德人工智能大学等机构创建了GEOBench-VLM数据集&#xff0c;目的评估视觉-语言模型&#xff08;VLM&#xff09;在地理空间任务中的表现。该数据集的推出填补了现有基准测试在地理空间应用中的空白&#xff0c;提供了超过10,000个经过人工验证的指…

南昌榉之乡托养机构解读:自闭症与看电视并无必然联系

在探讨自闭症的成因时&#xff0c;有人会问&#xff1a;自闭症是多看电视引起的吗&#xff1f;今天&#xff0c;就让我们来看看南昌榉之乡托养机构对此有何见解。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校&#xff0c;一直致力于为大龄自闭症患者提供专业的支持…

LabVIEW MathScript工具包对运行速度的影响及优化方法

LabVIEW 的 MathScript 工具包 在运行时可能会影响程序的运行速度&#xff0c;主要是由于以下几个原因&#xff1a; 1. 解释型语言执行方式 MathScript 使用的是类似于 MATLAB 的解释型语言&#xff0c;这意味着它不像编译型语言&#xff08;如 C、C 或 LabVIEW 本身的 VI&…

基于eFramework车控车设中间件介绍

车设的发展&#xff0c;起源于汽车工业萌芽之初&#xff0c;经历了机械式操作的原始粗犷&#xff0c;到电子式调控技术的巨大飞跃&#xff0c;到如今智能化座舱普及&#xff0c;远程车控已然成为汽车标配&#xff0c;车设功能选项也呈现出爆发式增长&#xff0c;渐趋多元繁杂。…

使用 AWR 进行 Exadata 性能诊断 - 2018版

本文和之前的使用 AWR 进行 Exadata 性能诊断是非常类似的&#xff0c;理论部分几乎一样&#xff0c;但案例部分是不同的&#xff0c;其价值也在于此。前文是基于Exadata X10&#xff0c;本文是基于Exadata X5。当然&#xff0c;型号并不重要&#xff0c;重要的是分析过程。 本…

【AI系统】计算与调度

计算与调度 上一篇文章我们了解了什么是算子&#xff0c;神经网络模型中由大量的算子来组成&#xff0c;但是算子之间是如何执行的&#xff1f;组成算子的算法逻辑跟具体的硬件指令代码之间的调度是如何配合&#xff1f;这些内容将会在本文进行深入介绍。 计算与调度 计算与…

JavaSE学习心得(APL与算法篇)

常用APL和常见算法 前言 常用APL Math System Runtime Object ​编辑浅克隆 深克隆 Objects Biginteger 构造方法 成员方法 底层存储方式 Bigdecimal 构造方法 Bigdecimal的使用 底层存储方式 ​编辑正则表达式 两个判断练习 两个爬取练习 贪婪爬取和非贪…

C++ ——— 引用的概念以及特性

目录 引用的概念 引用在实际代码中的作用 引用的特性 1. 引用在定义时必须初始化 2. 一个变量可以有多个引用 3. 可以给别名再次取别名&#xff0c;或者多次取别名 4. 引用一旦引用了实体&#xff0c;就不能再引用其他实体了 引用的概念 引用不是新定义一个变量&#x…

Linux-异步IO和存储映射IO

异步IO 在 I/O 多路复用中&#xff0c;进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中&#xff0c;当文件描述符上可以执行 I/O 操作时&#xff0c;进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其它的任务…

嵌入式入门Day23

数据结构Day4 操作受限的线性表栈基本概念顺序栈顺序栈结构创建顺序栈判空和判满栈扩容入栈出栈遍历销毁栈 链式栈队列基本概念顺序队列循环顺序队列定义循环队列的创建循环顺序队列的判空和判满循环顺序队列的入队循环顺序队列的遍历循环顺序队列的出队循环顺序队列的销毁 链式…

C语言实验 一维数组

时间:2024.12.3 一、实验 7-1 交换最小值和最大值 #include<stdio.h> int main() {int n, a[10], i, min = 0, max = 0;scanf("%d", &n);for (i = 0; i < n; i++){scanf("%d",&a[i]);}for (i = 0; i < n; i++){if (a[min] > a[i…