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() 一次全部清掉,切换场景时也可以调用。