Unity3D 观察者模式

news2024/11/24 20:23:52

Unity3D 泛型事件系统

观察者模式

观察者模式是一种行为设计模式,通过订阅机制,可以让对象触发事件时,通知多个其他对象。

在游戏逻辑中,UI 界面通常会监听一些事件,当数据层发生变化时,通过触发事件,通知 UI 界面进行刷新。

定义事件类型

先进行简单的一步,创建 GameEventType.cs 脚本,定义一个枚举类型,可以在枚举中添加多个事件名。

public enum GameEventType
{
    PlayerAttack,  // 玩家攻击
    PlayerDeath,   // 玩家阵亡
}

事件管理器

接着,创建 EventManager.cs 脚本,定义多个泛型委托,这里声明了单参数和两个参数的委托,参数类型是泛型 T。

using System;
using System.Collections.Generic;

// 单参数事件处理委托
public delegate void EventDelegate<T>(T param);

// 两个参数的事件处理委托
public delegate void EventDelegate<T1, T2>(T1 param1, T2 param2);

public class EventManager
{
    
}

在 EventManager 类中,定义两个字典,分别存储单参数和两个参数的委托列表。

public class EventManager
{
    // 单参数事件的字典,键是事件类型,值是对应的事件处理器
    static Dictionary<int, Delegate> eventTableSingle = new Dictionary<int, Delegate>();

    // 两个参数事件的字典
    static Dictionary<int, Delegate> eventTableDouble = new Dictionary<int, Delegate>();
}

然后分别添加三个接口:订阅、取消订阅、触发。

  • 订阅,接收事件名和函数,判断字典中是否存在事件名,不存在则添加新的事件,然后把函数连接到委托中。
  • 取消订阅,接收事件名和函数,判断字典中是否存在事件名,存在则从委托中移除函数。
  • 触发,接收事件名和参数,判断字典中是否存在事件名,存在则取出委托并调用。

如果后续还需要三个参数,可以依此类推,添加字典和接口。

public class EventManager
{
    // ...
    
    // 订阅单参数事件
    public static void AddListener<T>(GameEventType gameEventType, EventDelegate<T> handler)
    {
        int eventType = (int)gameEventType;
        if (!eventTableSingle.ContainsKey(eventType))
        {
            eventTableSingle.Add(eventType, null);
        }
        eventTableSingle[eventType] = (EventDelegate<T>)eventTableSingle[eventType] + handler;
    }

    // 取消订阅单参数事件
    public static void RemoveListener<T>(GameEventType gameEventType, EventDelegate<T> handler)
    {
        int eventType = (int)gameEventType;
        if (eventTableSingle.ContainsKey(eventType))
        {
            eventTableSingle[eventType] = (EventDelegate<T>)eventTableSingle[eventType] - handler;
        }
    }

    // 触发单参数事件
    public static void Trigger<T>(GameEventType gameEventType, T param)
    {
        int eventType = (int)gameEventType;
        if (eventTableSingle.ContainsKey(eventType))
        {
            var callback = eventTableSingle[eventType] as EventDelegate<T>;
            callback?.Invoke(param);
        }
    }

    // 订阅双参数事件
    public static void AddListener<T1, T2>(GameEventType gameEventType, EventDelegate<T1, T2> handler)
    {
        int eventType = (int)gameEventType;
        if (!eventTableDouble.ContainsKey(eventType))
        {
            eventTableDouble.Add(eventType, null);
        }
        eventTableDouble[eventType] = (EventDelegate<T1, T2>)eventTableDouble[eventType] + handler;
    }

    // 取消订阅双参数事件
    public static void RemoveListener<T1, T2>(GameEventType gameEventType, EventDelegate<T1, T2> handler)
    {
        int eventType = (int)gameEventType;
        if (eventTableDouble.ContainsKey(eventType))
        {
            eventTableDouble[eventType] = (EventDelegate<T1, T2>)eventTableDouble[eventType] - handler;
        }
    }

    // 触发双参数事件
    public static void Trigger<T1, T2>(GameEventType gameEventType, T1 param1, T2 param2)
    {
        int eventType = (int)gameEventType;
        if (eventTableDouble.ContainsKey(eventType))
        {
            var callback = eventTableDouble[eventType] as EventDelegate<T1, T2>;
            callback?.Invoke(param1, param2);
        }
    }
}

添加和移除监听

创建 PlayerEvent.cs 脚本,在场景中也创建一个游戏物体,挂载该脚本。

添加事件监听

在 OnEnable 方法中,调用 EventManager.AddListener 添加事件监听。

在 OnDisable 方法中,调用 EventManager.RemoveListener 移除事件监听。

此时可以确定泛型参数的实际类型,并在回调函数中接收参数,进行逻辑处理。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerEvent : MonoBehaviour
{
    void OnEnable()
    {
        EventManager.AddListener<int>(GameEventType.PlayerAttack, OnPlayerAttack);
        EventManager.AddListener<string, int>(GameEventType.PlayerDeath, OnPlayerDeath);
    }

    void OnDisable()
    {
        EventManager.RemoveListener<int>(GameEventType.PlayerAttack, OnPlayerAttack);
        EventManager.RemoveListener<string, int>(GameEventType.PlayerDeath, OnPlayerDeath);
    }

    void OnPlayerAttack(int damage)
    {
        Debug.Log($"玩家发起攻击,造成伤害 {damage}");
    }

    void OnPlayerDeath(string reason, int damage)
    {
        Debug.Log($"玩家阵亡,原因 {reason},受到伤害 {damage}");
    }
}

触发事件

创建 PlayerEventTest.cs 脚本,在 Update 方法中,根据键盘按键,触发不同的事件。

这里定义 PlayerEvent 变量,按下 E 键对其游戏物体进行显示隐藏,是为了测试事件的添加和移除。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerEventTest : MonoBehaviour
{
    public PlayerEvent playerEvent;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            EventManager.Trigger(GameEventType.PlayerAttack, 100);
        }
        else if (Input.GetKeyDown(KeyCode.W))
        {
            EventManager.Trigger(GameEventType.PlayerDeath, "Boss攻击", 999);
        }
        else if (Input.GetKeyDown(KeyCode.E))
        {
            bool isActive = playerEvent.gameObject.activeInHierarchy;
            playerEvent.gameObject.SetActive(!isActive);
        }
    }
}

在场景中添加游戏物体,并挂载该脚本,拖拽引用。

触发事件

运行游戏:

  • 按下 Q 键触发了 PlayerAttack 事件
  • 按下 W 键触发了 PlayerDeath 事件
  • 按下 E 键隐藏了 PlayerEvent 游戏物体,同时事件被移除,不会再响应 Q 和 W 键,除非再次按下 E 键,显示游戏物体并添加事件。

如图所示:

运行效果

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

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

相关文章

【JavaSE基础】Java 变量

为什么需要变量 变量是程序的基本组成单位 class Test{public static void main(String[] args){int a 1; //定义一个变量&#xff0c;类型为int&#xff0c;变量名为a&#xff0c;并赋值为1int b 3; //定义另一个变量&#xff0c;类型为int&#xff0c;变量名为b&#xff0…

sqli-labs less-25 and/or绕过

来到less-25 我们可以看到下面有提示&#xff0c;Hint: Your Input is Filtered with following result: 说明本关卡有过滤&#xff0c; 构造 http://192.168.140.130/sq/Less-25/?id1’ 页面报错&#xff0c;从报错可以得知闭合方式为,所以 用注释符&#xff0c;发现注释符…

oracle数据坏块处理(一)-通过rman备份修复

表有坏块时&#xff0c;全表查询会报错&#xff1a; 这时候如果有前面正常的rman备份&#xff0c;那么我们就可以通过rman备份直接对数据文件块做恢复 先对数据文件做个逻辑检查&#xff1a; RMAN> backup check logical VALIDATE DATAFILE EXB_DATA/exb/datafile/cuteinf…

公开课 | 2024最新清华大模型公开课 第3课 神经网络与大模型基础 Part 2

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 大纲 神经网络概述 神经网络的概念 神经网络的应用方式 序列建模与神经网络架构 循环神经网络&#xff08;RNN&#xff09;…

UE5模型导入面板解读

1.Skeletal Mesh&#xff1a; 是一个可以让模型动起来的选项&#xff0c;适用于需要动画的角色或生物。是否勾选&#xff1a;如果导入的是一个需要动画的角色或生物&#xff0c;就勾选 Skeletal Mesh 选项&#xff1b;如果是静态物体&#xff0c;就不勾选。 2.Build Nanite&a…

集合类HashMap,HashTable,ConcurrentHashMap区别?

1.HashMap 简单来说&#xff0c;HashMap由数组链表组成的&#xff0c;数组是HashMap的主体&#xff0c;链表则是主要为了解决哈希冲突而存在的&#xff0c;如果定位到的数组位置不含链表&#xff08;当前entry的next指向null&#xff09;,那么对于查找&#xff0c;添加等操作很…

VS中创建QT项目。

一&#xff0c;安装QT&#xff0c; 重点&#xff1a;在安装QT的时候要安装msvc201x版本的组件&#xff0c; 二 &#xff0c; 安装 qt-vs-tools Index of /development_releases/vsaddin/2.8.1 三。安装 win10sdk&#xff0c;这是因为我的当前电脑是win10的&#xff0c; 安装版…

【逗号绕过】

简介 所以为了避免逗号被过滤&#xff0c;我们来看看如何绕过叭 一、From for 绕过 我们直接看一个题目&#xff1a; id1 页面输出hello user id1 and 11%23 页面返回hello user id1 and 11%23 页面不返回数据符合盲注&#xff0c;并且是一个数字型的sql注入&#xff0c;尝…

13.梯度下降法的代码实战——举足轻重的模型优化算法

引言 通过12.梯度下降法的具体解析——举足轻重的模型优化算法-CSDN博客的学习&#xff0c;我们已经了解到了梯度下降法的整体流程与不同分类。归根结底&#xff0c;我们最终是要使用代码实现梯度下降法。 通过阅读本篇博客&#xff0c;你可以&#xff1a; 1.知晓轮次和批次…

Unity URP 如何实现遮挡显示 (全流程教程)

嗨~~&#xff01;&#xff0c;熊猫老师又来了 &#xff0c;这次为大家分享项目中非常实用的一个技术点&#xff1a;遮挡显示。 老规矩&#xff0c;上才艺&#xff1a; 实现原理 &#xff1a;对模型渲染两次。 第一次&#xff1a; 正常渲染物体&#xff0c;深度测试不通过的情况…

【工具】HTTrack:网站一键克隆下载,实现离线浏览与备份的利器

什么是 HTTrack&#xff1f; HTTrack 是一款用于复制完整网站的开源工具&#xff0c;它可以从服务器下载整个网站的内容&#xff0c;包括 HTML 文件、图像、样式表、脚本等资源。通过这种方式&#xff0c;你可以在离线状态下浏览网站&#xff0c;就像在线一样。 HTTrack 支持…

设备台账管理是什么

设备管理对企业至关重要。比如在电子加工企业&#xff0c;高效的设备管理能减少设备故障&#xff0c;提升生产效率&#xff0c;为企业赢得市场竞争优势。设备台账管理作为设备管理的一个核心部分&#xff0c;起着重要的作用。 让我们一起从本篇文章中探索设备台账管理是什么&a…

[STM32] 简单介绍 (一)

文章目录 1.STM32简介2.ARM3.STM32F103ZET6/STM32F103C8T64.STM32命名规则5.STM32最小系统板6.STM32开发方式7.STM32系统架构8.STM32时钟系统9.STM32中断系统10.STM32定时器 1.STM32简介 STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器&#xff1b; STM32常应用在嵌入式…

【最新华为OD机试E卷-支持在线评测】高矮个子排队(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

基于rk356x u-boot版本功能分析及编译相关(一)

🎏技术驱动源于热爱,祝各位学有所成。 文章目录 uboot的分支是next-dev历史版本v2017-09uboot支持DM框架uboot前级pre-loader支持及引导下级uboot分区支持uboot支持固件格式secure bootuboot编译脚本位置build.shuboot/make.shrkbin仓库uboot的分支是next-dev历史版本v2017-…

Xilinx远程固件升级(一)——QuickBoot方案

Xilinx 7系FPGA远程更新方案——QuickBoot方式远程更新bit 一、远程更新背景和架构 对于非ZYNQ系列的常规FPGA来说&#xff0c;对于bit的更新一般使用JTAG进行烧录。而作为商用产品&#xff0c;想要进行OTA升级时&#xff0c;使用JTAG的升级方式显然不适合&#xff0c;因此&a…

数据结构与算法:数组与链表的扩展与应用

数据结构与算法&#xff1a;数组与链表的扩展与应用 数组和链表是数据结构中的基础内容&#xff0c;但它们的变体和扩展在实际应用中同样至关重要。通过深入理解数组和链表的内存布局、动态管理以及高级操作&#xff0c;我们可以更有效地选择和设计适合特定应用场景的数据结构…

分布式事务管理-Seata从入门到精通

一、基本概念 什么是数据库事务&#xff1f; 1、一个操作数据库数据的执行单元 2、到围从开始到结束的多个操作组成 3、事务内的多个操作要么都成功,要么都失败 什么是分布式事务&#xff1f; 1.分布式场景下&#xff0c;完成某一个业务功能可能需要横跨多个服务&#xff0…

NFT Insider #151:The Sandbox 推出 Alpha 第4季;腾讯或将收购育碧

市场数据 加密艺术及收藏品新闻 Beeple 将于 11 月在南京德基美术馆举办个人首展 著名数字艺术家 Beeple 近日在X平台发布视频&#xff0c;宣布将于 2024 年 11 月 14 日在南京德基美术馆举办个人首次展览&#xff0c;名为《Beeple&#xff1a;来自合成未来的故事》。该展览将…

JavaScript进阶--深入面向对象

深入面向对象 编程思想 面向过程&#xff1a;多个步骤> 解决问题 性能较高&#xff0c;适合跟硬件联系很紧密的东西&#xff0c;如单片机 但代码维护成本高&#xff0c;扩展性差 面向对象&#xff1a;问题所需功能分解为一个一个的对象&#xff08;分工合作&#xff09;>…