【行为型之观察者模式】游戏开发实战——Unity事件驱动架构的核心实现策略
文章目录
- 🎯 观察者模式(Observer Pattern)深度解析
- 一、模式本质与核心价值
- 二、经典UML结构
- 三、Unity实战代码(玩家血量监控系统)
- 1. 定义观察者接口与主题基类
- 2. 实现具体主题(玩家血量)
- 3. 实现具体观察者
- 4. 客户端使用
- 四、模式进阶技巧
- 1. 事件总线系统
- 2. 条件过滤器
- 3. 异步事件处理
- 五、游戏开发典型应用场景
- 六、性能优化策略
- 七、模式对比与选择
- 八、最佳实践原则
- 九、常见问题解决方案
🎯 观察者模式(Observer Pattern)深度解析
——以Unity实现动态事件通知与跨系统响应为核心案例
一、模式本质与核心价值
核心目标:
✅ 建立对象间的一对多依赖,实现状态变化自动通知
✅ 解耦主题与观察者,提升系统扩展性与维护性
✅ 支持动态订阅机制,灵活管理观察关系
关键术语:
- Subject(主题):状态变化的对象(如玩家血量)
- Observer(观察者):监听状态变化的对象(如UI、成就系统)
- Notification(通知):主题向观察者传递的信息
数学表达:
设主题S有观察者集合O={o₁, o₂, …, oₙ},当S变化时:
S.Update() → ∀o ∈ O, o.OnNotify()
二、经典UML结构
三、Unity实战代码(玩家血量监控系统)
1. 定义观察者接口与主题基类
public interface IObserver {void OnNotify(HealthData data);
}public interface ISubject {void RegisterObserver(IObserver observer);void RemoveObserver(IObserver observer);void NotifyObservers();
}[System.Serializable]
public struct HealthData {public float CurrentHealth;public float MaxHealth;public float DamageAmount;
}
2. 实现具体主题(玩家血量)
public class PlayerHealth : MonoBehaviour, ISubject {[SerializeField] private float maxHealth = 100f;private float _currentHealth;private List<IObserver> _observers = new();void Start() {_currentHealth = maxHealth;}public void TakeDamage(float damage) {_currentHealth = Mathf.Max(_currentHealth - damage, 0);NotifyObservers(new HealthData {CurrentHealth = _currentHealth,MaxHealth = maxHealth,DamageAmount = damage});if(_currentHealth <= 0) Die();}public void RegisterObserver(IObserver observer) => _observers.Add(observer);public void RemoveObserver(IObserver observer) => _observers.Remove(observer);private void NotifyObservers(HealthData data) {foreach(var observer in _observers) {observer.OnNotify(data);}}
}
3. 实现具体观察者
// 血条UI
public class HealthBarUI : MonoBehaviour, IObserver {[SerializeField] private Image healthFill;public void OnNotify(HealthData data) {healthFill.fillAmount = data.CurrentHealth / data.MaxHealth;Debug.Log($"血条更新:{healthFill.fillAmount:P0}");}
}// 成就系统
public class AchievementTracker : MonoBehaviour, IObserver {private int _consecutiveHits;public void OnNotify(HealthData data) {if(data.DamageAmount > 0) {_consecutiveHits++;if(_consecutiveHits >= 3) {Debug.Log("解锁成就:连续受伤三次!");}} else {_consecutiveHits = 0;}}
}
4. 客户端使用
public class GameManager : MonoBehaviour {[SerializeField] private PlayerHealth playerHealth;[SerializeField] private HealthBarUI healthUI;[SerializeField] private AchievementTracker achievementTracker;void Start() {playerHealth.RegisterObserver(healthUI);playerHealth.RegisterObserver(achievementTracker);}void Update() {if(Input.GetKeyDown(KeyCode.Space)) {playerHealth.TakeDamage(10);}}
}
四、模式进阶技巧
1. 事件总线系统
public static class EventBus {private static Dictionary<Type, List<Action<object>>> _handlers = new();public static void Subscribe<T>(Action<T> handler) {Type type = typeof(T);if(!_handlers.ContainsKey(type)) _handlers[type] = new List<Action<object>>();_handlers[type].Add(obj => handler((T)obj));}public static void Publish<T>(T eventData) {Type type = typeof(T);if(_handlers.TryGetValue(type, out var handlers)) {foreach(var h in handlers) h(eventData);}}
}// 使用示例
EventBus.Subscribe<HealthData>(data => {// 处理健康数据...
});
2. 条件过滤器
public class FilteredObserver : IObserver {private Predicate<HealthData> _filter;private Action<HealthData> _action;public FilteredObserver(Predicate<HealthData> filter, Action<HealthData> action) {_filter = filter;_action = action;}public void OnNotify(HealthData data) {if(_filter(data)) _action(data);}
}// 使用示例:仅在血量低于30%时触发
var lowHealthObserver = new FilteredObserver(data => data.CurrentHealth/data.MaxHealth < 0.3f,data => ShowWarning()
);
3. 异步事件处理
public class AsyncEventProcessor : MonoBehaviour {private Queue<HealthData> _eventQueue = new();public void QueueEvent(HealthData data) {_eventQueue.Enqueue(data);}void Update() {while(_eventQueue.Count > 0) {StartCoroutine(ProcessEvent(_eventQueue.Dequeue()));}}private IEnumerator ProcessEvent(HealthData data) {// 异步处理逻辑yield return null;}
}
五、游戏开发典型应用场景
-
成就系统触发
public class AchievementSystem : IObserver {public void OnNotify(EnemyDeathData data) {if(data.EnemyType == EnemyType.Boss) {UnlockAchievement("BOSS_SLAYER");}} }
-
全局事件通知
public class GlobalEvent : ISubject {private static GlobalEvent _instance;public static GlobalEvent Instance => _instance ??= new GlobalEvent();// 实现观察者注册/通知逻辑... }
-
技能冷却系统
public class SkillManager : IObserver {public void OnNotify(SkillEventData data) {if(data.EventType == SkillEventType.Used) {StartCooldown(data.SkillID);}} }
-
环境互动反馈
public class EnvironmentFX : IObserver {public void OnNotify(PlayerMoveData data) {if(data.IsInWater) PlayWaterRippleFX(data.Position);} }
六、性能优化策略
策略 | 实现方式 | 适用场景 |
---|---|---|
事件过滤 | 前置条件检查 | 高频事件 |
批处理 | 合并多个事件 | 物理系统更新 |
对象池 | 重用事件对象 | 频繁事件触发 |
分层处理 | 优先级队列 | 关键事件优先 |
七、模式对比与选择
维度 | 观察者模式 | 发布-订阅模式 |
---|---|---|
耦合度 | 观察者直接注册到主题 | 通过中间件解耦 |
灵活性 | 需要知道具体主题 | 无需知道发布者 |
性能 | 直接调用更高效 | 中间件可能引入开销 |
典型应用 | 组件间直接通信 | 系统级事件管理 |
八、最佳实践原则
- 避免过度通知:仅在状态真正变化时触发通知
private float _previousHealth;void Update() {if(Mathf.Abs(_currentHealth - _previousHealth) > 0.01f) {NotifyObservers();_previousHealth = _currentHealth;} }
- 内存管理:及时取消不再需要的订阅
void OnDestroy() {playerHealth.RemoveObserver(this); }
- 线程安全:在多线程环境使用锁机制
private readonly object _lock = new object(); public void RegisterObserver(IObserver observer) {lock(_lock) {_observers.Add(observer);} }
- 事件数据不可变:
public readonly struct ImmutableHealthData {public readonly float Current;public ImmutableHealthData(float current) => Current = current; }
九、常见问题解决方案
Q1:如何处理循环通知?
→ 实现事件标记防止递归
private bool _isNotifying;
public void NotifyObservers() {if(_isNotifying) return;_isNotifying = true;// 通知逻辑..._isNotifying = false;
}
Q2:如何优化大量观察者的性能?
→ 使用分层观察者
public class TieredObserverSystem {private Dictionary<EventPriority, List<IObserver>> _tiers = new();public void NotifyByPriority() {foreach(var tier in Enum.GetValues(typeof(EventPriority))) {foreach(var observer in _tiers[(EventPriority)tier]) {observer.OnNotify();}}}
}
Q3:如何调试复杂事件流?
→ 实现事件追踪器
public class EventDebugger : IObserver {public void OnNotify(object data) {Debug.Log($"[Event] {DateTime.Now:HH:mm:ss.fff} - {data.GetType().Name}");// 记录到文件或调试窗口...}
}
上一篇 【行为型之备忘录模式】游戏开发实战——Unity存档系统与状态管理的终极解决方案
下一篇 【行为型之状态模式】深度剖析——Unity角色行为控制与AI决策的终极解决方案
相关文章:
【行为型之观察者模式】游戏开发实战——Unity事件驱动架构的核心实现策略
文章目录 🎯 观察者模式(Observer Pattern)深度解析一、模式本质与核心价值二、经典UML结构三、Unity实战代码(玩家血量监控系统)1. 定义观察者接口与主题基类2. 实现具体主题(玩家血量)3. 实现…...

神经网络语言模型(前馈神经网络语言模型)
神经网络语言模型 什么是神经网络?神经网络的基本结构是什么?输入层隐藏层输出层 神经网络为什么能解决问题?通用近似定理为什么需要权重和偏置?为什么需要激活函数?权重是如何确定的?1. 穷举2. 反向传播主…...
基于Transformer的多资产收益预测模型实战(附PyTorch实现与避坑指南)
基于Transformer的多资产收益预测模型实战(附PyTorch模型训练及可视化完整代码) 一、项目背景与目标 在量化投资领域,利用时间序列数据预测资产收益是核心任务之一。传统方法如LSTM难以捕捉资产间的复杂依赖关系,而Transformer架构通过自注意力机制能有效建模多资产间的联…...

CUDA编程——性能优化基本技巧
本文主要介绍下面三种技巧: 使用 __restrict__ 让编译器放心地优化指针访存想办法让同一个 Warp 中的线程的访存 Pattern 尽可能连续,以利用 Memory coalescing使用 Shared memory 0. 弄清Kernael函数是Compute-bound 还是 Memory-bound 先摆出一个知…...

道通EVO MAX系列无人机-支持二次开发
道通EVO MAX系列无人机-支持二次开发 EVO Max 系列采用Autel Autonomy自主飞行技术,实现复杂环境下的全局路径规划、3D场景重建、自主绕障和返航;高精度视觉导航能力,使其在信号干扰强、信号遮挡、信号弱等复杂环境下,依然获得高精…...
Node.js中MongoDB连接的进阶模块化封装
Node.js中MongoDB连接的进阶模块化封装 📑 目录 为什么需要模块化数据库连接现代Node.js连接MongoDB的最佳实践四层架构下的模块化封装 1. 配置层(Config Layer)2. 连接层(Connection Layer)3. 模型层(Mo…...
Go基于plugin的热更新初体验
背景 对于一个部署在生产环境的项目来说,我们希望当代码出现bug的时候,可以不用重启进程而达到动态修改代码的目的—— 这就是代码热部署! 使用java做游戏服务器,最大的好处是,当代码出现bug,可以直接热…...

计算机网络-MPLS LDP基础实验配置
前面我们学习了LDP的会话建立、标签发布与交换、LDP的工作原理,今天通过一个基础实验来加深记忆。 一、LDP基础实验 实验拓扑: 1、IGP使用OSPF进行通告,使用Lookback接口作为LSR ID,LDP ID自动生成。 2、实验目的:使…...

HPE ProLiant DL360 Gen11 服务器,配置 RAID 5 教程!
今天的任务,是帮客户的一台HPE ProLiant DL360 Gen11 服务器,配置RAID 5。依然是按照我的个人传统习惯,顺便做一个教程,分享给有需要的粉丝们。如果你在实际操作中,遇到了什么问题,欢迎在评论区留言&#x…...

SARIMA-LSTM融合模型对太阳黑子数量预测分析|附智能体数据代码
全文智能体链接:https://tecdat.cn/?p41969 分析师:Peng Fan 本研究以太阳黑子活动数据为研究对象,旨在帮助客户探索其未来走势并提供预测分析。首先,通过对数据的清洗和处理,包括离群值的识别与处理以及时间序列的建…...

C# WinForm DataGridView 非常频繁地更新或重新绘制慢问题及解决
非常频繁地更新 DataGridView问题描述: 在 C# 中无法在合理的时间内刷新我的 DataGridView ,我每秒通过网络发送 20 个数据包,获取数据。我想解析这些数据并将其放入 DataGridView 中。我还想调整 DataGridView 的更新间隔,从 0.1…...

【数据结构】红黑树(C++)
目录 一、红黑树的概念 二、红黑树的性质 三、红黑树结点定义 四、红黑树的操作 1. 插入操作 1.1 插入过程 1.2 调整过程 1.2.1 叔叔节点存在且为红色 1.2.2 叔叔节点存在且为黑色 1.2.3 叔叔节点不存在 2. 查找操作 2.1 查找逻辑 2.2 算法流程图 2.3 使用示例 …...
验证码与登录过程逻辑学习总结
目录 前言 一、验证码与登录 二、使用步骤 1.先apipost测试一波 2.先搞验证码 3.跨域问题 4.后端走起 总结 前言 近期要做一个比较完整的demo,需要自己做一个前端登录页面,不过api接口都是现成的,一开始以为过程会很easy,…...

Android Framework学习五:APP启动过程原理及速度优化
文章目录 APP启动优化概述APP启动流程点击图片启动APP的过程启动触发Zygote 与应用进程创建Zygote进程的创建应用进程初始化 ApplicationActivity 启动与显示 优化启动时黑白屏现象可优化的阶段Application阶段相关优化 Activity阶段数据加载阶段 Framework学习系列文章 APP启动…...

Meta的AIGC视频生成模型——Emu Video
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video,作为Meta发布的第二款视频生成模型,在视频生成领域发挥关键作用。 🌺优质专栏回顾&am…...

Axure难点解决分享:统计分析页面引入Echarts示例动态效果
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…...

Docker 常见问题及其解决方案
一、安装与启动问题 1.1 安装失败 在不同操作系统上安装 Docker 时,可能会出现安装失败的情况。例如,在 Ubuntu 系统中,执行安装命令后提示依赖缺失。这通常是因为软件源配置不正确或系统缺少必要的依赖包。 解决方案: 确保系统…...
后端开发面试高频50个问题,简单解答
以下是后端开发面试中常见的50个高频问题及其详细解答,涵盖了语言基础、数据库、网络、操作系统、设计模式等多个方面: 编程语言基础 Java 中的 final 关键字有什么作用? final 可以修饰类、方法和变量。修饰类时,类不能被继承&am…...

IC解析之TPS92682-Q1(汽车LED灯控制IC)
目录 1 IC特性介绍2 主要参数3 接口定义4 工作原理分析TPS92682-Q1架构工作模式典型应用通讯协议 控制帧应答帧协议5 总结 1 IC特性介绍 TPS92682 - Q1 是德州仪器(TI)推出的一款双通道恒压横流控制器,同时还具有各种电器故障保护,…...

6.01 Python中打开usb相机并进行显示
本案例介绍如何打开USB相机并每隔100ms进行刷新的代码,效果如下: 一、主要思路: 1. 打开视频流、读取帧 self.cam_cap = cv2.VideoCapture(0) #打开 视频流 cam_ret, cam_frame = self.cam_cap.read() //读取帧。 2.使用定时器,每隔100ms读取帧 3.显示到Qt的QLabel…...

2023华为od统一考试B卷【二叉树中序遍历】
前言 博主刷的华为机考题,代码仅供参考,因为没有后台数据,可能有没考虑到的情况 如果感觉对你有帮助,请点点关注点点赞吧,谢谢你! 题目描述 思路 0.用Character数组存储树,index下标的左右…...
计算机网络:计算机之间的数据传输为什么要以时钟频率同步为基础?
以太网信息同步需要保障时钟同步的主要原因包括以下几点: 1. 确保数据的准确采样与解析 比特级同步:以太网数据传输以连续的比特流形式进行,接收端需在精确的时间点对信号采样。若发送端与接收端时钟不同步,采样时机偏移会导致误…...

在Spark搭建YARN
(一)什么是SparkONYarn模式 Spark on YARN(Yet Another Resource Negotiator)是 Spark 框架在 Hadoop 集群中运行的一种部署模式,它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager:作…...

LeetCode_sql刷题(3482.分析组织层级)
题目描述:3482. 分析组织层级 - 力扣(LeetCode) 表:Employees ------------------------- | Column Name | Type | ------------------------- | employee_id | int | | employee_name | varchar | | manager_id …...
Python 之 selenium 打开浏览器指定端口进行接续操作
一般使用 selenium 进行数据爬取时,常用处理流程是让 selenium 从打开浏览器开始,完成全流程的所有操作。但是有时候,我们希望用户先自己打开浏览器进入指定网页,完成登录认证等一系列操作之后(比如用户、密码、短信验…...
MySQL Explain 中 Type 与 Extra 字段详解
引言 在数据库性能调优过程中,理解执行计划(EXPLAIN)的输出信息至关重要。MySQL 的 EXPLAIN 命令能够帮助开发者分析查询的执行路径和效率,其中 Type 和 Extra 字段提供了关键的执行细节。Type 字段表示访问类型,反映…...

不用服务器转码,Web端如何播放RTSP视频流?
在物联网、智慧城市、工业互联网等新兴技术浪潮下,实时视频流(如RTSP协议)作为安防监控、生产巡检、远程协作等场景的核心数据载体,其价值愈发凸显。然而,一个长期困扰行业的痛点始终存在——如何在Web浏览器中直接播…...

如何开发一款 Chrome 浏览器插件
Chrome是由谷歌开发的网页浏览器,基于开源软件(包括WebKit和Mozilla)开发,任何人都可以根据自己需要使用、修改或增强它的功能。Chrome凭借着其优秀的性能、出色的兼容性以及丰富的扩展程序,赢得了广大用户的信任。市场…...

GitHub打开缓慢甚至失败的解决办法
在C:\Windows\System32\drivers\etc的hosts中增加如下内容: 20.205.243.166 github.com 199.59.149.236 github.global.ssl.fastly.net185.199.109.153 http://assets-cdn.github.com 185.199.108.153 http://assets-cdn.github.com 185.199.110.153 http://asset…...

18前端项目----Vue项目收尾优化|重要知识
收尾/知识点汇总 项目收尾二级路由未登录全局路由守卫路由独享守卫图片懒加载路由懒加载打包上线 重要知识点汇总组件通信方式1. props2. 自定义事件3. 全局事件总线4. 订阅与发布pubsub5. Vuex6. 插槽 sync修饰符attrs和listeners属性children和parent属性mixin混入作用域插槽…...