Unity RPG地牢游戏中基于观察者模式的事件选择性更新方案问询
嘿,这个问题我在Unity做RPG地牢游戏的时候也碰到过——单一观察者主题下全量通知确实会搞出很多没必要的性能开销,还容易让逻辑变得乱糟糟的。针对你说的场景,其实可以给观察者模式加个事件类型过滤的机制,让每个观察者只订阅自己关心的事件,触发时只通知对应类型的观察者就行。
核心思路:给事件分类,让观察者精准订阅
不要让所有观察者都盯着同一个“大主题”,而是给主题里的事件做细分,比如把事件分成PlayerShoot、EnemyTakeDamage、ItemInteract这类具体类型,每个观察者只订阅自己需要响应的事件类型,触发时只遍历对应类型的观察者列表。
具体实现步骤(Unity C#示例)
1. 定义事件类型枚举
先把你游戏里可能出现的事件都归类,用枚举清晰区分:
public enum GameEventType { PlayerShoot, // 玩家射箭事件 EnemyDetectPlayer, // 敌人发现玩家事件 ItemPickup, // 物品拾取事件 DoorOpen // 门打开事件 // 可以根据需求继续扩展 }
2. 重构GameEventManager:按事件类型管理观察者
原来的单一观察者列表改成字典结构,key是事件类型,value是对应类型的观察者列表。这样触发事件时,只需要找到对应类型的列表通知就行:
public class GameEventManager : MonoBehaviour { // 单例模式,方便全局调用 public static GameEventManager Instance { get; private set; } // 按事件类型存储观察者列表 private Dictionary<GameEventType, List<IGameEventListener>> _eventListeners = new Dictionary<GameEventType, List<IGameEventListener>>(); private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; DontDestroyOnLoad(gameObject); } // 初始化所有事件类型的空列表 foreach (GameEventType type in Enum.GetValues(typeof(GameEventType))) { if (!_eventListeners.ContainsKey(type)) { _eventListeners[type] = new List<IGameEventListener>(); } } } // 订阅事件:观察者指定要监听的事件类型 public void Subscribe(GameEventType eventType, IGameEventListener listener) { if (_eventListeners.TryGetValue(eventType, out var listeners)) { if (!listeners.Contains(listener)) { listeners.Add(listener); } } } // 取消订阅 public void Unsubscribe(GameEventType eventType, IGameEventListener listener) { if (_eventListeners.TryGetValue(eventType, out var listeners)) { if (listeners.Contains(listener)) { listeners.Remove(listener); } } } // 触发事件:只通知对应类型的观察者 public void TriggerEvent(GameEventType eventType, object eventData = null) { if (_eventListeners.TryGetValue(eventType, out var listeners)) { // 遍历副本,避免遍历中列表修改导致的异常 foreach (var listener in listeners.ToList()) { listener.OnEventTriggered(eventType, eventData); } } } }
3. 定义观察者接口
让所有需要响应事件的对象实现这个接口,接口里的方法可以接收事件类型和额外数据(比如射箭时的方向、位置):
public interface IGameEventListener { void OnEventTriggered(GameEventType eventType, object eventData); }
4. 具体观察者实现(比如玩家、敌人、交互物品)
- 敌人类:只订阅
PlayerShoot事件来做出躲避/反击反应:
public class Enemy : MonoBehaviour, IGameEventListener { private void OnEnable() { // 敌人只关心玩家射箭的事件 GameEventManager.Instance.Subscribe(GameEventType.PlayerShoot, this); } private void OnDisable() { GameEventManager.Instance.Unsubscribe(GameEventType.PlayerShoot, this); } public void OnEventTriggered(GameEventType eventType, object eventData) { if (eventType == GameEventType.PlayerShoot) { // 敌人做出躲避或反击逻辑 Debug.Log("Enemy reacts to player's shoot!"); // 用事件数据做精准反应,比如转向射箭方向 Vector3 shootPos = (Vector3)eventData; transform.LookAt(shootPos); } } }
- 地面交互物品:订阅
PlayerShoot事件,被箭射中时触发效果:
public class InteractiveItem : MonoBehaviour, IGameEventListener { private void OnEnable() { // 物品关心被箭射中的事件 GameEventManager.Instance.Subscribe(GameEventType.PlayerShoot, this); } private void OnDisable() { GameEventManager.Instance.Unsubscribe(GameEventType.PlayerShoot, this); } public void OnEventTriggered(GameEventType eventType, object eventData) { if (eventType == GameEventType.PlayerShoot) { // 检查是否被箭击中 RaycastHit hit = (RaycastHit)eventData; if (hit.collider == GetComponent<Collider>()) { Debug.Log("Interactive item triggered by player's shoot!"); // 执行物品逻辑:爆炸、触发机关等 } } } }
5. 触发事件的示例(玩家按下空格键射箭)
public class PlayerController : MonoBehaviour { private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { ShootArrow(); // 获取射箭的射线检测结果作为事件数据 RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit)) { // 触发PlayerShoot事件,传递命中数据 GameEventManager.Instance.TriggerEvent(GameEventType.PlayerShoot, hit); } } } private void ShootArrow() { // 生成箭、播放动画等逻辑 Debug.Log("Player shoots arrow!"); } }
额外优化建议
- 避免空事件订阅:可以在GameEventManager里加日志,提醒开发者某个事件没有观察者,方便调试。
- 事件数据封装:如果事件数据比较复杂,可以创建专门的
GameEventData类,代替object类型,避免类型转换的麻烦。 - 性能考量:如果游戏里事件非常频繁,比如每帧都触发的事件,可以考虑用对象池缓存观察者列表的副本,减少
ToList()的开销。
这样一来,当玩家按下空格键触发PlayerShoot事件时,只有订阅了这个事件类型的观察者(比如敌人、可被箭触发的物品)会收到通知并更新,完全不会影响其他无关对象(比如门、未订阅该事件的NPC),完美解决你的问题。
内容的提问来源于stack exchange,提问作者Ben Schwabe
相关产品推荐
相关产品推荐

