461 字
2 分钟
游戏设计模式04——事件总线
NOTE事件总线只有一个通用的发布/订阅入口,新增事件时只需要定义一个事件类型,不用改总线本身。
事件总线核心代码
using System;using System.Collections.Generic;using UnityEngine;
/// <summary>/// 泛型事件总线:每种事件类型 T 自动拥有一份独立的监听列表。/// </summary>public static class EventBus<T> where T : struct{ private static Action<T> _handlers;
static EventBus() { // 第一次用到某个事件类型时,把它的清空方法登记到总表 EventBusRegistry.Register(Clear); }
public static void Subscribe(Action<T> handler) => _handlers += handler; public static void Unsubscribe(Action<T> handler) => _handlers -= handler; public static void Publish(T evt) => _handlers?.Invoke(evt); public static void Clear() => _handlers = null;}
/// <summary>/// 记录所有用过的事件类型,用于统一清空。/// </summary>public static class EventBusRegistry{ private static readonly List<Action> _clearActions = new();
public static void Register(Action clear) => _clearActions.Add(clear);
public static void ClearAll() { foreach (var clear in _clearActions) clear(); }
// 进入 Play 模式时清空,防止关闭 Domain Reload 后残留旧订阅 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetOnLoad() => ClearAll();}定义事件
一个事件就是一个 struct,可以放在任何文件里。
public readonly struct SelectEvent { }
public readonly struct ScoreChangedEvent{ public readonly int Score; public ScoreChangedEvent(int score) => Score = score;}订阅与发布
using UnityEngine;
public class ScoreUI : MonoBehaviour{ private void OnEnable() => EventBus<ScoreChangedEvent>.Subscribe(OnScoreChanged); private void OnDisable() => EventBus<ScoreChangedEvent>.Unsubscribe(OnScoreChanged);
private void OnScoreChanged(ScoreChangedEvent e) { Debug.Log($"当前分数:{e.Score}"); }}
// 任意位置发布EventBus<ScoreChangedEvent>.Publish(new ScoreChangedEvent(100));EventBus<SelectEvent>.Publish(default);设计要点
- 新增事件不用改总线: 只要新建一个
struct,多人协作时也不会都去改同一个文件。 - 性能优秀:
EventBus<T>对每个T都是一个独立的静态类,没有字典查找、装箱和类型转换。 - 类型安全: 事件类型写错会编译报错,参数也有明确的字段。
- 可以统一清空:
EventBusRegistry会记下所有用过的事件类型,ClearAll()一次全部清掉,切换场景时也可以调用。