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的点云框选方法一样的软件,因此,博主想到利用投影的形式进行解决: 具体的,…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...
