5.事件中心模块

5.事件中心模块


5.1 知识点

事件中心模块的作用

在游戏开发中,我们经常遇到这样的问题:不同模块之间需要相互通信,但直接引用会造成高耦合

核心问题:

  • 模块间直接引用导致高耦合,难以维护
  • 一个模块的修改可能影响多个其他模块
  • 代码复用性差,测试困难

解决方案:
事件中心模块通过观察者模式实现模块间的松耦合通信,让模块之间不需要直接引用,而是通过事件进行通信。



事件中心模块的基本原理

设计理念:

  • 观察者模式:事件发布者和订阅者解耦
  • 集中管理:通过事件中心统一管理所有事件
  • 类型安全:支持泛型参数传递,避免装箱拆箱
  • 统一命名:通过枚举管理事件名称

工作流程:

  1. 注册监听:模块向事件中心注册感兴趣的事件
  2. 触发事件:某个模块触发事件,传递相关数据
  3. 事件分发:事件中心将事件分发给所有监听者
  4. 执行回调:监听者执行相应的处理逻辑


事件中心模块基础实现

using System.Collections.Generic;
using UnityEngine.Events;

/// <summary>
/// 事件中心模块(初步版本 - 存在参数传递问题)
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    #region 字段

    /// <summary>
    /// 事件字典
    /// 键为事件名称(字符串),值为对应的委托
    /// </summary>
    private Dictionary<string, UnityAction> _eventDict = new Dictionary<string, UnityAction>();

    #endregion

    #region 构造函数

    private EventCenter()
    {
    }

    #endregion

    #region 事件管理

    /// <summary>
    /// 添加事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener(string eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName] += func;
        }
        else
        {
            _eventDict.Add(eventName, func);
        }
    }

    /// <summary>
    /// 移除事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener(string eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName] -= func;
        }
    }

    /// <summary>
    /// 触发事件
    /// </summary>
    /// <param name="eventName">事件名称</param>
    public void EventTrigger(string eventName)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName]?.Invoke();
        }
    }

    #endregion
}

初步测试代码:Monster.csPlayer.cs

// Monster.cs
public class Monster : MonoBehaviour
{
    public string monsterName = "哥布林";
    public int monsterID = 1;

    public void Dead()
    {
        Debug.Log("怪物死亡了");
        // 触发怪物死亡事件
        EventCenter.Instance.EventTrigger("MonsterDead");
        // 问题:无法传递怪物信息给监听者
    }
}

// Player.cs
public class Player : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener("MonsterDead", OnMonsterDead);
    }

    private void OnMonsterDead()
    {
        Debug.Log("玩家获得奖励");
        // 输出:玩家获得奖励
        // 问题:无法获取具体是哪个怪物死亡,无法获取怪物信息
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener("MonsterDead", OnMonsterDead);
    }
}

事件中心传递参数

之前存在什么问题

通过上面的测试代码,我们发现了一个严重的问题:事件触发时无法传递参数,监听者无法获取具体的事件信息

问题分析:

  1. 无法传递数据:事件触发时无法传递相关数据
  2. 信息缺失:监听者无法获取具体的事件信息
  3. 功能受限:无法实现复杂的事件处理逻辑

如何解决参数传递问题

为了支持事件参数传递,我们需要修改事件中心的实现。最初的想法是使用object类型来传递参数。

解决方案:

  1. 使用object类型:利用里式替换原则,父类容器装子类对象
  2. 修改委托类型:使用UnityAction<object>支持参数传递
  3. 类型转换:在监听者中进行类型转换

解决后的源码实现:EventCenter.cs (支持object参数传递)

using System.Collections.Generic;
using UnityEngine.Events;

/// <summary>
/// 事件中心模块(支持object参数传递,存在装箱拆箱问题)
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    #region 字段

    /// <summary>
    /// 事件字典
    /// 键为事件名称(字符串),值为对应的委托
    /// </summary>
    private Dictionary<string, UnityAction<object>> _eventDict = new Dictionary<string, UnityAction<object>>();

    #endregion

    #region 构造函数

    private EventCenter()
    {
    }

    #endregion

    #region 事件管理

    /// <summary>
    /// 添加事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener(string eventName, UnityAction<object> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName] += func;
        }
        else
        {
            _eventDict.Add(eventName, func);
        }
    }

    /// <summary>
    /// 移除事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener(string eventName, UnityAction<object> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName] -= func;
        }
    }

    /// <summary>
    /// 触发事件
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="info">事件参数</param>
    public void EventTrigger(string eventName, object info)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict[eventName]?.Invoke(info);
        }
    }

    #endregion
}

测试代码:Monster.csPlayer.cs (更新)

// Monster.cs (更新后,支持参数传递)
public class Monster : MonoBehaviour
{
    public string monsterName = "哥布林";
    public int monsterID = 1;

    public void Dead()
    {
        Debug.Log("怪物死亡了");
        // 触发怪物死亡事件,传递怪物信息
        EventCenter.Instance.EventTrigger("MonsterDead", this);
        // 输出:怪物死亡了
    }
}

// Player.cs (更新后,支持接收参数)
public class Player : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener("MonsterDead", OnMonsterDead);
    }

    private void OnMonsterDead(object monsterObj)
    {
        // 类型转换获取怪物信息
        Monster monster = monsterObj as Monster;
        if (monster != null)
        {
            Debug.Log($"玩家获得奖励:{monster.monsterName}");
            // 输出:玩家获得奖励:哥布林
        }
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener("MonsterDead", OnMonsterDead);
    }
}

事件中心参数类型自定义

之前存在什么问题

通过上面的实现,我们解决了参数传递问题,但引入了新的问题:使用object类型传递值类型数据时存在装箱拆箱,增加性能开销

问题分析:

  1. 装箱拆箱:值类型传递时发生装箱拆箱,影响性能
  2. 类型不安全:需要手动类型转换,容易出错
  3. 代码冗余:每次都需要进行类型转换

如何解决参数类型自定义问题

为了支持任意类型的参数传递,同时避免装箱拆箱问题,我们需要使用泛型来实现类型安全的事件参数传递。

解决方案:

  1. 使用泛型:支持任意类型的参数传递
  2. 里式替换原则:通过基类容器管理不同类型的EventInfo
  3. 类型安全:编译时确定类型,避免运行时类型转换

解决后的源码实现:EventInfoBase.csEventInfo.cs

// EventInfoBase.cs
/// <summary>
/// 事件信息基类
/// 主要用于里式替换原则,父类容器装子类对象
/// </summary>
public abstract class EventInfoBase
{
}

// EventInfo.cs
using UnityEngine.Events;

/// <summary>
/// 有参数事件信息类
/// 用来包裹对应观察者函数委托的类
/// </summary>
/// <typeparam name="T">事件参数类型</typeparam>
public class EventInfo<T> : EventInfoBase
{
    /// <summary>
    /// 真正观察者对应的函数信息
    /// </summary>
    public UnityAction<T> Actions;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="action">事件处理函数</param>
    public EventInfo(UnityAction<T> action)
    {
        Actions += action;
    }
}

/// <summary>
/// 无参数事件信息类
/// 主要用来记录无参无返回值委托
/// </summary>
public class EventInfo : EventInfoBase
{
    /// <summary>
    /// 无参数事件处理函数
    /// </summary>
    public UnityAction Actions;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="action">事件处理函数</param>
    public EventInfo(UnityAction action)
    {
        Actions += action;
    }
}

解决后的源码实现:EventCenter.cs (支持泛型参数传递)

using System.Collections.Generic;
using UnityEngine.Events;

/// <summary>
/// 事件中心模块(支持泛型参数传递,类型安全)
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    #region 字段

    /// <summary>
    /// 事件字典
    /// 键为事件名称(字符串),值为对应的事件信息基类
    /// </summary>
    private Dictionary<string, EventInfoBase> _eventDict = new Dictionary<string, EventInfoBase>();

    #endregion

    #region 构造函数

    private EventCenter()
    {
    }

    #endregion

    #region 事件管理

    /// <summary>
    /// 添加有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener<T>(string eventName, UnityAction<T> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo<T>(func));
        }
    }

    /// <summary>
    /// 添加无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener(string eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo(func));
        }
    }

    /// <summary>
    /// 移除有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener<T>(string eventName, UnityAction<T> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions -= func;
        }
    }

    /// <summary>
    /// 移除无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener(string eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions -= func;
        }
    }

    /// <summary>
    /// 触发有参数事件
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="info">事件参数</param>
    public void EventTrigger<T>(string eventName, T info)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>)?.Actions?.Invoke(info);
        }
    }

    /// <summary>
    /// 触发无参数事件
    /// </summary>
    /// <param name="eventName">事件名称</param>
    public void EventTrigger(string eventName)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo)?.Actions?.Invoke();
        }
    }

    #endregion
}

测试代码:Monster.csPlayer.cs (最终版本)

// Monster.cs (最终版本,支持泛型参数传递)
public class Monster : MonoBehaviour
{
    public string monsterName = "哥布林";
    public int monsterID = 1;

    public void Dead()
    {
        Debug.Log("怪物死亡了");
        // 触发怪物死亡事件,传递怪物信息(类型安全)
        EventCenter.Instance.EventTrigger<Monster>("MonsterDead", this);
        // 输出:怪物死亡了
    }
}

// Player.cs (最终版本,支持泛型参数接收)
public class Player : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件(类型安全)
        EventCenter.Instance.AddEventListener<Monster>("MonsterDead", OnMonsterDead);
    }

    private void OnMonsterDead(Monster monster)
    {
        // 直接使用怪物对象,无需类型转换
        Debug.Log($"玩家获得奖励:{monster.monsterName}");
        // 输出:玩家获得奖励:哥布林
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener<Monster>("MonsterDead", OnMonsterDead);
    }
}

事件中心事件名优化

之前存在什么问题

通过上面的实现,我们解决了参数传递和类型安全问题,但还存在一个问题:使用字符串作为事件名称容易拼写错误,导致事件无法正确触发或监听

问题分析:

  1. 拼写错误:字符串容易拼写错误,导致事件失效
  2. 难以维护:事件名称分散在各个地方,难以统一管理
  3. IDE支持差:无法享受IDE的智能提示和重构功能

如何解决事件名优化问题

为了统一管理事件名称,避免拼写错误,我们需要使用枚举来管理所有事件类型。

解决方案:

  1. 使用枚举:统一管理所有事件类型
  2. 类型安全:编译时检查事件名称
  3. IDE支持:享受智能提示和重构功能

解决后的源码实现:E_EventType.cs

/// <summary>
/// 事件类型枚举
/// 定义游戏中所有可能触发的事件类型
/// </summary>
public enum E_EventType
{
    #region 游戏逻辑事件

    /// <summary>
    /// 怪物死亡事件
    /// 参数:Monster
    /// </summary>
    E_Monster_Dead,

    /// <summary>
    /// 玩家获取奖励事件
    /// 参数:int
    /// </summary>
    E_Player_GetReward,

    /// <summary>
    /// 测试用事件
    /// 参数:无
    /// </summary>
    E_Test,

    /// <summary>
    /// 场景切换时进度变化事件
    /// 参数:float
    /// </summary>
    E_SceneLoadChange,

    #endregion

    #region 输入系统事件

    /// <summary>
    /// 输入系统触发技能1行为
    /// 参数:无
    /// </summary>
    E_Input_Skill1,

    /// <summary>
    /// 输入系统触发技能2行为
    /// 参数:无
    /// </summary>
    E_Input_Skill2,

    /// <summary>
    /// 输入系统触发技能3行为
    /// 参数:无
    /// </summary>
    E_Input_Skill3,

    /// <summary>
    /// 水平轴输入事件
    /// 参数:float (-1~1)
    /// </summary>
    E_Input_Horizontal,

    /// <summary>
    /// 垂直轴输入事件
    /// 参数:float (-1~1)
    /// </summary>
    E_Input_Vertical,

    #endregion
}

解决后的源码实现:EventCenter.cs (使用枚举管理事件名称)

using System.Collections.Generic;
using UnityEngine.Events;

/// <summary>
/// 事件中心模块(使用枚举管理事件名称,类型安全)
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    #region 字段

    /// <summary>
    /// 事件字典
    /// 键为事件类型枚举,值为对应的事件信息基类
    /// </summary>
    private Dictionary<E_EventType, EventInfoBase> _eventDict = new Dictionary<E_EventType, EventInfoBase>();

    #endregion

    #region 构造函数

    private EventCenter()
    {
    }

    #endregion

    #region 事件管理

    /// <summary>
    /// 添加有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件类型</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener<T>(E_EventType eventName, UnityAction<T> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo<T>(func));
        }
    }

    /// <summary>
    /// 添加无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件类型</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener(E_EventType eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo(func));
        }
    }

    /// <summary>
    /// 移除有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件类型</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener<T>(E_EventType eventName, UnityAction<T> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions -= func;
        }
    }

    /// <summary>
    /// 移除无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件类型</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener(E_EventType eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions -= func;
        }
    }

    /// <summary>
    /// 触发有参数事件
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件类型</param>
    /// <param name="info">事件参数</param>
    public void EventTrigger<T>(E_EventType eventName, T info)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>)?.Actions?.Invoke(info);
        }
    }

    /// <summary>
    /// 触发无参数事件
    /// </summary>
    /// <param name="eventName">事件类型</param>
    public void EventTrigger(E_EventType eventName)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo)?.Actions?.Invoke();
        }
    }

    #endregion

    #region 清理方法

    /// <summary>
    /// 清空所有事件的监听
    /// </summary>
    public void Clear()
    {
        _eventDict.Clear();
    }

    /// <summary>
    /// 清除指定某一个事件的所有监听
    /// </summary>
    /// <param name="eventName">要清除的事件类型</param>
    public void Clear(E_EventType eventName)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict.Remove(eventName);
        }
    }

    #endregion
}

测试代码:Monster.csPlayer.cs (最终版本)

// Monster.cs (最终版本,使用枚举管理事件名称)
public class Monster : MonoBehaviour
{
    public string monsterName = "哥布林";
    public int monsterID = 1;

    public void Dead()
    {
        Debug.Log("怪物死亡了");
        // 触发怪物死亡事件,使用枚举类型(类型安全)
        EventCenter.Instance.EventTrigger<Monster>(E_EventType.E_Monster_Dead, this);
        // 输出:怪物死亡了
    }
}

// Player.cs (最终版本,使用枚举管理事件名称)
public class Player : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件,使用枚举类型(类型安全)
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
        // 监听测试事件
        EventCenter.Instance.AddEventListener(E_EventType.E_Test, OnTest);
    }

    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"玩家获得奖励:{monster.monsterName}");
        // 输出:玩家获得奖励:哥布林
    }

    private void OnTest()
    {
        Debug.Log("无参事件监听者");
        // 输出:无参事件监听者
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
        EventCenter.Instance.RemoveEventListener(E_EventType.E_Test, OnTest);
    }
}

测试和使用

创建多模块事件监听测试

创建一个完整的测试场景,展示事件中心如何实现模块间的松耦合通信。

步骤说明:

  1. 创建多个模块(任务系统、奖励系统、其他系统)
  2. 让这些模块都监听同一个事件
  3. 触发事件,验证所有模块都能正确响应

测试代码:

// Task.cs - 任务系统
public class Task : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }

    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"任务记录:{monster.monsterName}");
        // 输出:任务记录:哥布林
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }
}

// RewardSystem.cs - 奖励系统
public class RewardSystem : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }

    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"奖励系统:获得经验值 {monster.monsterID * 10}");
        // 输出:奖励系统:获得经验值 10
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }
}

// OtherSystem.cs - 其他系统
public class OtherSystem : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }

    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"其他系统:怪物 {monster.monsterID} 已处理");
        // 输出:其他系统:怪物 1 已处理
    }

    private void OnDestroy()
    {
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }
}

创建事件触发测试

创建一个测试脚本来触发事件,验证事件中心的功能。

步骤说明:

  1. 创建测试脚本
  2. 在Start方法中触发各种事件
  3. 验证所有监听者都能正确响应

测试代码:

// EventCenterTest.cs
public class EventCenterTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("事件中心测试:开始");
        
        // 创建怪物对象
        Monster monster = new Monster();
        monster.monsterName = "测试怪物";
        monster.monsterID = 999;

        // 触发怪物死亡事件
        monster.Dead();
        // 输出:怪物死亡了
        // 输出:玩家获得奖励:测试怪物
        // 输出:任务记录:测试怪物
        // 输出:奖励系统:获得经验值 9990
        // 输出:其他系统:怪物 999 已处理

        // 触发无参数测试事件
        EventCenter.Instance.EventTrigger(E_EventType.E_Test);
        // 输出:无参事件监听者

        // 触发玩家获得奖励事件
        EventCenter.Instance.EventTrigger<int>(E_EventType.E_Player_GetReward, 100);
        // 输出:玩家获得奖励:100

        Debug.Log("事件中心测试:完成");
    }
}

拓展和进阶

可以使用struct继承事件接口定义事件,struct里写好这个事件需要的参数,这样一个struct可以描述事件类型和所需要的参数了。
其次可以定义个订阅Token类,取消订阅传Token。


5.2 知识点代码

EventCenter.cs(事件中心模块)

using System.Collections.Generic;
using UnityEngine.Events;

/// <summary>
/// 事件中心模块
/// 实现观察者模式,管理游戏中的事件监听和触发
/// 支持有参数和无参数的事件处理
/// </summary>
public class EventCenter : Singleton<EventCenter>
{
    #region 字段

    /// <summary>
    /// 事件字典
    /// 用于记录对应事件关联的对应逻辑
    /// </summary>
    private Dictionary<E_EventType, EventInfoBase> _eventDict = new Dictionary<E_EventType, EventInfoBase>();

    #endregion

    #region 构造函数

    private EventCenter()
    {
    }

    #endregion

    #region 事件触发

    /// <summary>
    /// 触发有参数事件
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="info">事件参数</param>
    public void EventTrigger<T>(E_EventType eventName, T info)
    {
        // 存在关心此事件的监听者才通知处理逻辑
        if (_eventDict.ContainsKey(eventName))
        {
            // 执行对应的逻辑
            (_eventDict[eventName] as EventInfo<T>)?.Actions?.Invoke(info);
        }
    }

    /// <summary>
    /// 触发无参数事件
    /// </summary>
    /// <param name="eventName">事件名称</param>
    public void EventTrigger(E_EventType eventName)
    {
        // 存在关心此事件的监听者才通知处理逻辑
        if (_eventDict.ContainsKey(eventName))
        {
            // 执行对应的逻辑
            (_eventDict[eventName] as EventInfo)?.Actions?.Invoke();
        }
    }

    #endregion
    
    #region 事件监听管理

    /// <summary>
    /// 添加有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener<T>(E_EventType eventName, UnityAction<T> func)
    {
        // 如果已经存在关心事件的委托记录,直接添加即可
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo<T>(func));
        }
    }

    /// <summary>
    /// 添加无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">事件处理函数</param>
    public void AddEventListener(E_EventType eventName, UnityAction func)
    {
        // 如果已经存在关心事件的委托记录,直接添加即可
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions += func;
        }
        else
        {
            _eventDict.Add(eventName, new EventInfo(func));
        }
    }

    /// <summary>
    /// 移除有参数事件监听者
    /// </summary>
    /// <typeparam name="T">事件参数类型</typeparam>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener<T>(E_EventType eventName, UnityAction<T> func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo<T>).Actions -= func;
        }
    }

    /// <summary>
    /// 移除无参数事件监听者
    /// </summary>
    /// <param name="eventName">事件名称</param>
    /// <param name="func">要移除的事件处理函数</param>
    public void RemoveEventListener(E_EventType eventName, UnityAction func)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            (_eventDict[eventName] as EventInfo).Actions -= func;
        }
    }

    #endregion

    #region 清理方法

    /// <summary>
    /// 清空所有事件的监听
    /// </summary>
    public void Clear()
    {
        _eventDict.Clear();
    }

    /// <summary>
    /// 清除指定某一个事件的所有监听
    /// </summary>
    /// <param name="eventName">要清除的事件名称</param>
    public void Clear(E_EventType eventName)
    {
        if (_eventDict.ContainsKey(eventName))
        {
            _eventDict.Remove(eventName);
        }
    }

    #endregion
}

EventInfoBase.cs(事件信息基类)

/// <summary>
/// 事件信息基类
/// 主要用于里式替换原则,父类容器装子类对象
/// </summary>
public abstract class EventInfoBase
{
}

EventInfo.cs(事件信息类)

using UnityEngine.Events;

/// <summary>
/// 有参数事件信息类
/// 用来包裹对应观察者函数委托的类
/// </summary>
/// <typeparam name="T">事件参数类型</typeparam>
public class EventInfo<T> : EventInfoBase
{
    /// <summary>
    /// 真正观察者对应的函数信息
    /// </summary>
    public UnityAction<T> Actions;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="action">事件处理函数</param>
    public EventInfo(UnityAction<T> action)
    {
        Actions += action;
    }
}

/// <summary>
/// 无参数事件信息类
/// 主要用来记录无参无返回值委托
/// </summary>
public class EventInfo : EventInfoBase
{
    /// <summary>
    /// 无参数事件处理函数
    /// </summary>
    public UnityAction Actions;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="action">事件处理函数</param>
    public EventInfo(UnityAction action)
    {
        Actions += action;
    }
}

E_EventType.cs(事件类型枚举)

/// <summary>
/// 事件类型枚举
/// 定义游戏中所有可能触发的事件类型
/// </summary>
public enum E_EventType
{
    #region 游戏逻辑事件

    /// <summary>
    /// 怪物死亡事件
    /// 参数:Monster
    /// </summary>
    E_Monster_Dead,

    /// <summary>
    /// 玩家获取奖励事件
    /// 参数:int
    /// </summary>
    E_Player_GetReward,

    /// <summary>
    /// 测试用事件
    /// 参数:无
    /// </summary>
    E_Test,

    /// <summary>
    /// 场景切换时进度变化事件
    /// 参数:float
    /// </summary>
    E_SceneLoadChange,

    #endregion

    #region 输入系统事件

    /// <summary>
    /// 输入系统触发技能1行为
    /// 参数:无
    /// </summary>
    E_Input_Skill1,

    /// <summary>
    /// 输入系统触发技能2行为
    /// 参数:无
    /// </summary>
    E_Input_Skill2,

    /// <summary>
    /// 输入系统触发技能3行为
    /// 参数:无
    /// </summary>
    E_Input_Skill3,

    /// <summary>
    /// 水平轴输入事件
    /// 参数:float (-1~1)
    /// </summary>
    E_Input_Horizontal,

    /// <summary>
    /// 垂直轴输入事件
    /// 参数:float (-1~1)
    /// </summary>
    E_Input_Vertical,

    #endregion
}

Monster.cs(怪物类)

using UnityEngine;

/// <summary>
/// 怪物类
/// 演示如何触发事件
/// </summary>
public class Monster : MonoBehaviour
{
    public string monsterName = "哥布林";
    public int monsterID = 1;

    /// <summary>
    /// 怪物死亡方法
    /// </summary>
    public void Dead()
    {
        Debug.Log("怪物死亡了");
        // 触发怪物死亡事件,传递怪物信息
        EventCenter.Instance.EventTrigger<Monster>(E_EventType.E_Monster_Dead, this);
        // 输出:怪物死亡了
    }
}

Player.cs(玩家类)

using UnityEngine;

/// <summary>
/// 玩家类
/// 演示如何监听事件
/// </summary>
public class Player : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
        // 监听测试事件
        EventCenter.Instance.AddEventListener(E_EventType.E_Test, OnTest);
    }

    /// <summary>
    /// 怪物死亡事件处理
    /// </summary>
    /// <param name="monster">死亡的怪物</param>
    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"玩家获得奖励:{monster.monsterName}");
        // 输出:玩家获得奖励:哥布林
    }

    /// <summary>
    /// 测试事件处理
    /// </summary>
    private void OnTest()
    {
        Debug.Log("无参事件监听者");
        // 输出:无参事件监听者
    }

    private void OnDestroy()
    {
        // 移除事件监听,避免内存泄露
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
        EventCenter.Instance.RemoveEventListener(E_EventType.E_Test, OnTest);
    }
}

Task.cs(任务系统)

using UnityEngine;

/// <summary>
/// 任务系统
/// 演示多模块事件监听
/// </summary>
public class Task : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }

    /// <summary>
    /// 怪物死亡事件处理
    /// </summary>
    /// <param name="monster">死亡的怪物</param>
    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"任务记录:{monster.monsterName}");
        // 输出:任务记录:哥布林
    }

    private void OnDestroy()
    {
        // 移除事件监听
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }
}

RewardSystem.cs(奖励系统)

using UnityEngine;

/// <summary>
/// 奖励系统
/// 演示多模块事件监听
/// </summary>
public class RewardSystem : MonoBehaviour
{
    private void Awake()
    {
        // 监听怪物死亡事件
        EventCenter.Instance.AddEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }

    /// <summary>
    /// 怪物死亡事件处理
    /// </summary>
    /// <param name="monster">死亡的怪物</param>
    private void OnMonsterDead(Monster monster)
    {
        Debug.Log($"奖励系统:获得经验值 {monster.monsterID * 10}");
        // 输出:奖励系统:获得经验值 10
    }

    private void OnDestroy()
    {
        // 移除事件监听
        EventCenter.Instance.RemoveEventListener<Monster>(E_EventType.E_Monster_Dead, OnMonsterDead);
    }
}

EventCenterUsageTest.cs(事件中心使用测试)

using UnityEngine;

/// <summary>
/// 事件中心使用测试
/// 演示事件中心的完整功能
/// </summary>
public class EventCenterUsageTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("事件中心使用测试:开始");
        
        // 创建怪物对象
        Monster monster = new Monster();
        monster.monsterName = "测试怪物";
        monster.monsterID = 999;

        // 触发怪物死亡事件
        monster.Dead();
        // 输出:怪物死亡了
        // 输出:玩家获得奖励:测试怪物
        // 输出:任务记录:测试怪物
        // 输出:奖励系统:获得经验值 9990

        // 触发无参数测试事件
        EventCenter.Instance.EventTrigger(E_EventType.E_Test);
        // 输出:无参事件监听者

        // 触发玩家获得奖励事件
        EventCenter.Instance.EventTrigger<int>(E_EventType.E_Player_GetReward, 100);
        // 输出:玩家获得奖励:100

        Debug.Log("事件中心使用测试:完成");
    }
}

ComprehensiveEventTest.cs(综合事件测试)

using UnityEngine;

/// <summary>
/// 综合事件测试
/// 演示事件中心的所有功能
/// </summary>
public class ComprehensiveEventTest : MonoBehaviour
{
    void Start()
    {
        Debug.Log("综合事件测试:开始");

        // 测试有参数事件
        TestParameterizedEvents();
        
        // 测试无参数事件
        TestParameterlessEvents();
        
        // 测试事件清理
        TestEventClearing();
    }

    /// <summary>
    /// 测试有参数事件
    /// </summary>
    private void TestParameterizedEvents()
    {
        Debug.Log("测试有参数事件");
        
        // 创建测试怪物
        Monster monster = new Monster();
        monster.monsterName = "综合测试怪物";
        monster.monsterID = 888;

        // 触发怪物死亡事件
        monster.Dead();
        // 输出:怪物死亡了
        // 输出:玩家获得奖励:综合测试怪物
        // 输出:任务记录:综合测试怪物
        // 输出:奖励系统:获得经验值 8880
    }

    /// <summary>
    /// 测试无参数事件
    /// </summary>
    private void TestParameterlessEvents()
    {
        Debug.Log("测试无参数事件");
        
        // 触发测试事件
        EventCenter.Instance.EventTrigger(E_EventType.E_Test);
        // 输出:无参事件监听者

        // 触发技能事件
        EventCenter.Instance.EventTrigger(E_EventType.E_Input_Skill1);
        // 输出:技能1被触发
    }

    /// <summary>
    /// 测试事件清理
    /// </summary>
    private void TestEventClearing()
    {
        Debug.Log("测试事件清理");
        
        // 清理特定事件
        EventCenter.Instance.Clear(E_EventType.E_Test);
        Debug.Log("已清理测试事件");
        
        // 清理所有事件
        EventCenter.Instance.Clear();
        Debug.Log("已清理所有事件");
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏