【U3D/简单框架】3.事件中心模块及其优化

自我介绍

广东双非一本的大三小白,计科专业,想在制作毕设前夯实基础,毕设做出一款属于自己的游戏!

事件中心模块

  • 知识点:Dictionary,委托,观察者设计模式
  • 作用:降低程序耦合性,减少程序复杂度

简单的一个事件中心系统 EventCenter.cs

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

public class EventCenter : BaseSingleton<EventCenter>
{
    // key对应事件的名字,value对应的是监听这个事件对应的委托函数【们】
    private Dictionary<string, UnityAction> eventDic = new Dictionary<string, UnityAction>();

    // 添加事件监听
    public void AddEventListener(string name, UnityAction action)
    {
        if (eventDic.ContainsKey(name))
            eventDic[name] += action;
        else
            eventDic.Add(name, action);
    }

    // 事件触发
    public void EventTrigger(string name)
    {
        if (eventDic.ContainsKey(name))
            eventDic[name].Invoke();
    }

    // 一定要移除,不然可能会发生内存泄漏
    public void RemoveEventListen(string name, UnityAction action)
    {
        if (eventDic.ContainsKey(name))
            eventDic[name] -= action;
    }

    // 清空事件中心,主要用在场景切换时
    public void Clear()
    {
        eventDic.Clear();
    }
}

测试,比如怪物死亡,对应的玩家,分数事件之类的发生变化(以下代码均放到游戏物体上才可触发)

public class Monster : MonoBehaviour
{
    public string name = "123";

    private void Start()
    {
        Dead();
    }

    void Dead()
    {
        Debug.Log("怪物死亡");
        // 触发事件
        EventCenter.GetInstance().EventTrigger("MonsterDead");
    }
}
public class Player : MonoBehaviour
{
    private void Start()
    {
        EventCenter.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo);
    }
    
    private void OnDestroy() => EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);

    private void MonsterDeadDo() => Debug.Log("玩家得到奖励");
    
}
public class Task : MonoBehaviour
{
    private void Start()
    {
        EventCenter.GetInstance().AddEventListener("MonsterDead", TaskWaitMonsterDeadDo);
    }
    
    private void OnDestroy() => EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);
    
    public void TaskWaitMonsterDeadDo() => Debug.Log("任务  记录");
}

为什么要在Destroy中移除事件呢,比如玩家死亡,怪物在玩家之后死亡,他要执行玩家里的那个方法,所以玩家死亡的时候在内存里不会真正消失,因为怪物跟玩家有所关联,这就可能造成内存泄漏

为了让事件中心系统更加通用(处理多参数方法),我们需要升级我们的 EventCenter.cs

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

public class EventCenter : BaseManager<EventCenter>
{
    // key对应事件的名字,value对应的是监听这个事件对应的委托函数【们】
    private Dictionary<string, UnityAction<object>> eventDic = new Dictionary<string, UnityAction<object>>();

    // 添加事件监听
    public void AddEventListener(string name, UnityAction<object> action)
    {
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] += action;
        }
        else
        {
            eventDic.Add(name, action);
        }
    }

    // 事件触发
    public void EventTrigger(string name, object info)
    {
        if (eventDic.ContainsKey(name))
        {
            eventDic[name]?.Invoke(info);
        }
    }
    public void RemoveEventListen(string name, UnityAction<object> action)
    {
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] -= action;
        }
    }

    // 清空事件中心,主要用在场景切换时
    public void Clear()
    {
        eventDic.Clear();
    }
}

因为用到object,所以会有装箱拆箱的问题,不过为了更加通用,只能在使用的时候尽量不传递值类型,就不会发生装箱拆箱问题了

关键来了!我们在注册这个事件的时候!

Monster.cs

void Dead()
{
    Debug.Log("怪物死亡");
    // 触发事件
    EventCenter.GetInstance().EventTrigger("MonsterDead", this);
}

可以看到这里吧this传进去,那么怎么用呢?

修改一下player的代码,其他同理

public class Player : MonoBehaviour
{
    private void Start()
    {
        EventCenter.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo);
    }

    private void MonsterDeadDo(object info)
    {
        Debug.Log("玩家得到奖励:" + (info as Monster).name);
    }

    private void OnDestroy()
    {
        EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);
    }
}

可以把这个object当成Monster然后调用name


优化事件中心模块

现在的事件中心模块因为有object,会涉及到装箱拆箱问题,所以会有一定的性能损耗,所以存在优化的空间

涉及到里氏转换原则:基类装子类

灵活使用里氏转换原则,灵活运用空接口与泛型的配合,为什么要用空接口呢,因为如果要穿个T过去,就必须是 public class EventCenter<T> ,但EventCenter只有一个,比如传入了GameObject作为T就不能再改了

关于实现无参方法,依然要继承空接口,创建同名类即可(最好看下面代码)

先创建接口并对接口进行继承 EventInfo.cs

using UnityEngine.Events;

public interface IEventInfo { }
// 实现一个参数
public class EventInfo<T> : IEventInfo
{
    public UnityAction<T> actions;
    public EventInfo(UnityAction<T> action)
    {
        actions += action;
    }
}
// 实现无参
public class EventInfo : IEventInfo
{
    public UnityAction actions;
    public EventInfo(UnityAction action)
    {
        actions += action;
    }
}

升级 EventCenter.cs ,还添加了不需要参数触发的(重载)也行

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

public class EventCenter : BaseSingleton<EventCenter>
{
    // key对应事件的名字,value对应的是监听这个事件对应的委托函数【们】
    private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>();

    // 添加事件监听,一个参数的
    public void AddEventListener<T>(string name, UnityAction<T> action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo<T>).actions += action;
        else
            eventDic.Add(name, new EventInfo<T>(action));
    }

    // 添加事件监听,无参数的
    public void AddEventListener(string name, UnityAction action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo).actions += action;
        else
            eventDic.Add(name, new EventInfo(action));
    }

    // 事件触发,无参的
    public void EventTrigger(string name)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo).actions?.Invoke();
    }

    //事件触发,一个参数的
    public void EventTrigger<T>(string name, T info)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo<T>).actions?.Invoke(info);
    }

    //移除监听,无参的
    public void RemoveEventListener(string name, UnityAction action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo).actions -= action;
    }

    //移除监听,一个参数的
    public void RemoveEventListener<T>(string name, UnityAction<T> action)
    {
        if (eventDic.ContainsKey(name))
            (eventDic[name] as EventInfo<T>).actions -= action;
    }

    // 清空事件中心,主要用在场景切换时
    public void Clear()
    {
        eventDic.Clear();
    }
}

使用,还是拿之前的player举例

public class Player : MonoBehaviour
{
    private void Awake()
    {
        EventCenter.GetInstance().AddEventListener<Monster>("MonsterDead", MonsterDeadDo);
    }

    private void MonsterDeadDo(Monster info)
    {
        Debug.Log("玩家得到奖励:" + info.name);
    }

    private void OnDestroy()
    {
        EventCenter.GetInstance().RemoveEventListen<Monster>("MonsterDead", MonsterDeadDo);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章