MessageCenter带有缓存机制,解决先发布,后注册订阅的问题
你需要在Unity中实现一个支持“先发布后接收”的消息订阅/发布机制核心是当订阅者B晚于发布者A订阅某个消息时仍然能接收到A之前发布的该类型消息而不是丢失这条消息。这种机制的关键是缓存历史消息让迟到的订阅者能获取到已发布的消息。核心实现思路设计一个全局单例的消息中心MessageCenter统一管理消息的发布、订阅和缓存。维护两个核心字典消息缓存字典存储各类型消息的历史数据按消息类型分类。回调字典存储各类型消息对应的订阅者回调函数。发布消息时先通知当前已订阅的接收者再将消息存入缓存。订阅消息时先注册回调再检查缓存字典若有该类型的历史消息则立即触发回调。补充支持取消订阅、清空缓存避免内存泄漏。完整代码实现1. 核心消息中心MessageCenter.csusingSystem;usingSystem.Collections.Generic;usingUnityEngine;/// summary/// 全局消息中心支持先发布后接收的消息机制/// /summarypublicclassMessageCenter:MonoBehaviour{// 单例实例privatestaticMessageCenter_instance;publicstaticMessageCenterInstance{get{if(_instancenull){// 创建隐藏的全局物体挂载消息中心GameObjectgonewGameObject(MessageCenter);_instancego.AddComponentMessageCenter();// 场景切换不销毁DontDestroyOnLoad(go);}return_instance;}}// 存储消息回调Key消息类型Value该类型的所有回调privateDictionaryType,Delegate_messageCallbacksnewDictionaryType,Delegate();// 存储历史消息缓存Key消息类型Value该类型的最新缓存消息也可改为List存储多条privateDictionaryType,object_messageCachenewDictionaryType,object();// 线程锁保证多线程下的安全性privatereadonlyobject_lockObjnewobject();privatevoidAwake(){// 防止重复创建单例if(_instance!null_instance!this){Destroy(gameObject);}else{_instancethis;DontDestroyOnLoad(gameObject);}}/// summary/// 发布消息支持缓存/// /summary/// typeparam nameT消息类型可以是string、int、自定义类等/typeparam/// param namemessage消息内容/parampublicvoidPublishT(Tmessage){lock(_lockObj){TypemsgTypetypeof(T);// 1. 通知当前所有已订阅的接收者if(_messageCallbacks.TryGetValue(msgType,outDelegatecallback)){((ActionT)callback)?.Invoke(message);}// 2. 缓存这条消息覆盖同类型的旧缓存也可改为Add存储多条if(_messageCache.ContainsKey(msgType)){_messageCache[msgType]message;}else{_messageCache.Add(msgType,message);}}}/// summary/// 订阅消息会自动接收历史缓存消息/// /summary/// typeparam nameT消息类型/typeparam/// param nameonReceived接收消息的回调函数/parampublicvoidSubscribeT(ActionTonReceived){if(onReceivednull)return;lock(_lockObj){TypemsgTypetypeof(T);// 1. 注册回调函数if(_messageCallbacks.TryGetValue(msgType,outDelegateexistingCallback)){_messageCallbacks[msgType]Delegate.Combine(existingCallback,onReceived);}else{_messageCallbacks.Add(msgType,onReceived);}// 2. 检查是否有历史缓存消息有则立即触发回调if(_messageCache.TryGetValue(msgType,outobjectcachedMsg)){onReceived.Invoke((T)cachedMsg);}}}/// summary/// 取消订阅消息/// /summary/// typeparam nameT消息类型/typeparam/// param nameonReceived要取消的回调函数/parampublicvoidUnsubscribeT(ActionTonReceived){if(onReceivednull)return;lock(_lockObj){TypemsgTypetypeof(T);if(_messageCallbacks.TryGetValue(msgType,outDelegateexistingCallback)){DelegatenewCallbackDelegate.Remove(existingCallback,onReceived);if(newCallbacknull){_messageCallbacks.Remove(msgType);}else{_messageCallbacks[msgType]newCallback;}}}}/// summary/// 清空指定类型的消息缓存/// /summary/// typeparam nameT消息类型/typeparampublicvoidClearCacheT(){lock(_lockObj){TypemsgTypetypeof(T);if(_messageCache.ContainsKey(msgType)){_messageCache.Remove(msgType);}}}/// summary/// 清空所有消息缓存/// /summarypublicvoidClearAllCache(){lock(_lockObj){_messageCache.Clear();}}}2. 测试用发布者PublisherA.csusingUnityEngine;/// summary/// 发布者A先发布消息即使此时没有接收者/// /summarypublicclassPublisherA:MonoBehaviour{// 自定义消息类示例[System.Serializable]publicclassPlayerInfo{publicstringname;publicintlevel;}privatevoidStart(){// 场景启动后立即发布两条消息// 1. 发布字符串消息MessageCenter.Instance.Publishstring(Hello from Publisher A!);// 2. 发布自定义类消息PlayerInfoinfonewPlayerInfo{namePlayer1,level10};MessageCenter.Instance.PublishPlayerInfo(info);Debug.Log(Publisher A 已发布消息);}}3. 测试用接收者SubscriberB.csusingUnityEngine;/// summary/// 接收者B后订阅测试能否收到历史消息/// /summarypublicclassSubscriberB:MonoBehaviour{privatevoidOnEnable(){// 订阅字符串消息MessageCenter.Instance.Subscribestring(OnReceiveStringMsg);// 订阅自定义类消息MessageCenter.Instance.SubscribePublisherA.PlayerInfo(OnReceivePlayerInfoMsg);Debug.Log(Subscriber B 已订阅消息);}privatevoidOnDisable(){// 取消订阅避免内存泄漏MessageCenter.Instance.Unsubscribestring(OnReceiveStringMsg);MessageCenter.Instance.UnsubscribePublisherA.PlayerInfo(OnReceivePlayerInfoMsg);}/// summary/// 接收字符串消息的回调/// /summaryprivatevoidOnReceiveStringMsg(stringmsg){Debug.Log($Subscriber B 收到字符串消息{msg});}/// summary/// 接收自定义类消息的回调/// /summaryprivatevoidOnReceivePlayerInfoMsg(PublisherA.PlayerInfoinfo){Debug.Log($Subscriber B 收到玩家信息姓名{info.name}等级{info.level});}}测试步骤验证先发布后接收在Unity场景中创建3个空物体命名为MessageCenter挂载MessageCenter脚本单例会自动创建也可手动创建命名为PublisherA挂载PublisherA脚本启用状态命名为SubscriberB挂载SubscriberB脚本先禁用。运行游戏此时PublisherA会立即发布消息但SubscriberB未订阅消息会被缓存在运行时手动启用SubscriberB物体此时SubscriberB订阅消息会立即收到PublisherA之前发布的历史消息控制台会打印对应的接收日志。关键代码解释消息缓存逻辑_messageCache字典存储各类型的历史消息Publish时存入Subscribe时检查并触发回调这是实现“先发布后接收”的核心泛型设计支持任意类型的消息体string、int、自定义类等无需为每种消息写单独的逻辑线程安全加lock保证多线程下字典操作的安全性Unity主线程为主但做防护单例模式全局唯一的消息中心场景切换不销毁确保消息机制全局可用。总结核心是通过_messageCache字典缓存历史消息让迟到的订阅者能获取已发布的消息订阅时主动检查缓存并触发回调是实现“先发布后接收”的关键步骤泛型单例的设计让消息机制具备通用性和全局可用性同时取消订阅和清空缓存的方法能避免内存泄漏。扩展建议如果需要存储多条历史消息而非仅最新一条可将_messageCache的Value改为Listobject发布时Add订阅时遍历触发所有历史消息可增加消息优先级、延迟发布、一次性消息接收后自动清空缓存等扩展功能建议在游戏退出或场景卸载时调用ClearAllCache()清空缓存避免数据残留。