You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中观察者模式双向通知:是否需实现反观察者?

Unity中双向事件驱动Observer模式的优化方案

方案1:反观察者模式(双向Subject-Observer)

  • 核心逻辑:让Observer也实现Subject接口,原Subject同时作为Observer订阅Observer的事件。当Observer内部发生状态变化时,主动通知订阅它的Subject。
  • Unity实现示例:
    定义统一的交互接口,让双方互相实现:
    public interface IEventSubject
    {
        void RegisterObserver(IEventObserver observer);
        void UnregisterObserver(IEventObserver observer);
        void NotifyObservers(string eventType, object data);
    }
    
    public interface IEventObserver
    {
        void OnNotify(string eventType, object data);
    }
    
    比如玩家(原Subject)实现IEventSubject和IEventObserver,UI(原Observer)同理。玩家通知UI更新血量,UI触发技能时通知玩家执行对应逻辑。
  • 优缺点:解耦彻底,双方都是独立的事件节点,能无缝接入大系统;但需要维护双向注册/注销逻辑,Unity中必须注意对象销毁时的注销操作,防止空引用报错。

方案2:全局事件中心(Unity生态最优解)

  • 核心逻辑:引入一个全局单例的事件调度器,Subject和Observer只与事件中心交互,不需要互相持有引用。
  • Unity实现示例:
    public class EventCenter : MonoBehaviour
    {
        private static EventCenter _instance;
        public static EventCenter Instance => _instance;
    
        private readonly Dictionary<string, List<System.Action<object>>> _eventMap = new();
    
        private void Awake()
        {
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }
    
        public void Subscribe(string eventName, System.Action<object> callback)
        {
            if (_eventMap.ContainsKey(eventName))
            {
                _eventMap[eventName].Add(callback);
            }
            else
            {
                _eventMap[eventName] = new List<System.Action<object>> { callback };
            }
        }
    
        public void Unsubscribe(string eventName, System.Action<object> callback)
        {
            if (_eventMap.TryGetValue(eventName, out var callbacks))
            {
                callbacks.Remove(callback);
            }
        }
    
        public void Publish(string eventName, object data)
        {
            if (_eventMap.TryGetValue(eventName, out var callbacks))
            {
                foreach (var callback in callbacks.ToList())
                {
                    callback?.Invoke(data);
                }
            }
        }
    }
    
    使用方式:Subject通过EventCenter.Instance.Publish("EnemyDamaged", damageValue)发布事件;Observer订阅EventCenter.Instance.Subscribe("SkillTriggered", OnSkillTriggered),自身触发事件时再调用Publish通知。
  • 优缺点:完全解耦,Subject和Observer无需知晓对方存在,接入大系统时仅需统一事件命名规范;Unity中可通过DontDestroyOnLoad保证全局存在,配合对象销毁时的Unsubscribe避免内存泄漏。

方案3:改进现有标记通知方案(轻量迭代)

  • 核心逻辑:在现有基础上优化标记机制,避免简单布尔值的模糊性。
  • 实现方式:
    • 定义明确的事件枚举(如ObserverEventType.SkillUsed, ObserverEventType.CloseUI),替换布尔标记;
    • 在Observer中维护待处理事件队列,Subject在特定时机(如每帧、逻辑帧)主动拉取事件并处理:
      public class PlayerSubject : MonoBehaviour
      {
          [SerializeField] private UIObserver _uiObserver;
      
          private void FixedUpdate()
          {
              if (_uiObserver.HasPendingEvents())
              {
                  foreach (var evt in _uiObserver.TakePendingEvents())
                  {
                      HandleObserverEvent(evt);
                  }
              }
          }
      }
      
  • 优缺点:改动量极小,适合现有项目快速迭代;但Subject需要主动轮询,耦合度高于前两种方案。

选型建议

  • 长期维护、需接入大型系统的项目:优先选择全局事件中心,完全解耦且贴合Unity开发习惯;
  • 希望保留Observer模式语义的场景:可以采用反观察者模式,但要严格管控双向注册的生命周期;
  • 轻量需求、不想大规模改动现有代码:优化现有标记方案即可。

内容的提问来源于stack exchange,提问作者IndividualGames

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:45:47