Unity3D 观察者模式
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 键,显示游戏物体并添加事件。
如图所示:
相关文章:

Unity3D 观察者模式
Unity3D 泛型事件系统 观察者模式 观察者模式是一种行为设计模式,通过订阅机制,可以让对象触发事件时,通知多个其他对象。 在游戏逻辑中,UI 界面通常会监听一些事件,当数据层发生变化时,通过触发事件&am…...

vue从0开始的项目搭建(含环境配置)
一、环境准备 下载node.js 检查node.js版本 替换npm下载源 1.下载node.js: Node.js — 在任何地方运行 JavaScript (nodejs.org) 2.查看版本: windowsr输入cmd进入输入node -v命令查看版本号是否出现确认是否安装 2.替换npm下载源: npm config set registry https://reg…...

力扣61~65题
题61(中等): 分析: python代码: # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def rot…...

API接口开发流程与指南
API(应用程序编程接口)是现代软件开发中不可或缺的一部分,它允许不同的软件应用之间进行交互和数据交换。无论是调用第三方服务、集成内部系统还是开发微服务架构,API都扮演着关键角色。本文将为你提供一个API接口入门的详解&…...
如何在Android中进行日志打印和调试?
在Android开发中,日志打印和调试是开发者定位问题、优化性能和提升应用质量的重要手段。以下将详细阐述如何在Android中进行日志打印和调试,包括日志工具的使用、调试技巧以及实践中的最佳实践。 一、日志工具的使用 1. Log类 Android中的日志工具类是…...

Linux基本使用和程序部署
文章目录 一. Linux背景Linux发行版 二. Linux环境搭建Linux常见命令lspwdcdtouchcatmkdirrmcpmvtailvimgreppsnetstat管道 三. 搭建java部署环境安装jdk安装mysql部署Web项目到Linux 一. Linux背景 1969−1970年,⻉尔实验室的DennisRitchie和KenTompson开发了Unix操作系统. 他…...

照片编辑成动态视频用什么软件好
在数字时代,让照片动起来确实已成为一种流行的潮流和趋势。如今,市面上涌现出众多软件,它们不仅配备了丰富多样的动态效果和特效,还支持用户进行个性化的编辑和创作。无论你是希望将家庭合影转化为充满温情的动画,还是…...

JavaWeb合集-SpringBoot项目配套知识
四、SpringBoot项目配套知识 1、Springboot项目的创建 2、HTTP 概念: Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。 2.1 request 请求协议 浏览器向服务器发送请求的规则(get、post等)。 2.1.1 请…...

Electron入门笔记
Electron入门笔记 ElectronElectron 是什么Electron流程模型创建第一个Electron项目配置自动重启主进程和渲染进程通信打包应用 Electron Electron 是什么 跨平台的桌面应用开发框架使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium和 Node.js Electro…...

python 不相交集简介(并查集算法)【Introduction to Disjoint Set (Union-Find Algorithm)】
什么是不相交集数据结构? 如果两个集合没有任何共同元素,则它们被称为不相交集,集合的交集为空集。 存储不重叠或不相交元素子集的数据结构称为不相交集合数据结构。不相交集合数据结构支持以下操作: 1、将新集合添加到不相交集合…...

23种设计模式之工厂方法模式
文章目录 1. 简介2. 代码2.1 抽象类:Course.java2.2 产品A:JavaCourse.java2.3 产品B:PythonCourse.java2.4 工厂抽象类:CourseFactory.java2.5 产品A的工厂A:JavaCourseFactory.java2.6 产品B的工厂B:PyCo…...

Redis——事务
文章目录 Redis 事务Redis 的事务和 MySQL 事务的区别:事务操作MULTIEXECDISCARDWATCHUNWATCHwatch的实现原理 总结 Redis 事务 什么是事务 Redis 的事务和 MySQL 的事务 概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行 Redis 的事务和 MySQL 事务的区别:…...
Redis非关系型数据库操作命令大全
以下是 Redis 的常用操作命令大全,涵盖了键值操作、字符串、哈希、列表、集合、有序集合、发布/订阅、事务等多个方面的操作。 1. 通用键命令 命令说明SET key value设置指定 key 的值GET key获取指定 key 的值DEL key删除指定的 keyEXISTS key检查 key 是否存在E…...

基于SpringBoot+Vue+uniapp微信小程序的澡堂预订的微信小程序的详细设计和实现
项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…...

Linux mips架构链接库函数调用plt表汇编代码分析
linux调用共享库中的函数时通过plt表和got表实现位置无关代码,过程中涉及到lazy binding,即在第一调用外部函数时解析被调用的函数地址并将地址写入到got表,后续调用则不需要解析函数地址。这一部分和硬件架构有关,具体的是和cpu指…...

python 作业1
任务1: python为主的工作是很少的 学习的python的优势在于制作工具,制作合适的工具可以提高我们在工作中的工作效率的工具 提高我们的竞争优势。 任务2: 不换行 换行 任务3: 安装pycharm 进入相应网站Download PyCharm: The Python IDE for data science and we…...
Apache 出现 “403 forbidden“ 排查方法
1、检查运行 Apache 进程的用户没有对目录具备读取权限 如果该用户没有对 Directory 指定的目录具备适当的读取权限,就会导致 403 错误。 例如:使用用户apache启动Apache进程,但是apache用户对 Directory 指定的目录没有读取权限 2、检查…...
vue video播放m3u8监控视频
很关键的问题 vite创建的项目不需要import ‘videojs-contrib-hls’ 导入就报错 直接添加如下代码即可 html5: {vhs: {overrideNative: true},nativeVideoTracks: false,nativeAudioTracks: false,nativeTextTracks: false} 下面是完整组件示例 <template><div>…...

uniapp 获取签名证书 SHA1 自有证书签名打包
1.登录你的Dcloud 账户 2.找到我的应用菜单 3.点开某个应用 4.查看证书详情,里面有SHA1 和别名,密码,下载证书用于云打包,可以选择自有证书,输入别名,密码打包...
Open3d开发点云标注工具问题总结(二)
前面我们介绍了使用AABB方式来框选点云,但这种方式还是不够直观,我们的构想是设计一个和o3d.visualization.VisualizerWithEditing的点云框选方法一样的软件,因此,博主想到利用投影的形式进行解决: 具体的,…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...

【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...

SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf I2S简介 I2S(Inter-Integrated Circuit Sound)是一种用于传输数字音频数据的通信协议,广泛应用于音频设备中。 ESP32-S3 包含 2 个 I2S 外设,通过配置…...

篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...