自我介绍
广东双非一本的大三小白,计科专业,想在制作毕设前夯实基础,毕设做出一款属于自己的游戏!
事件中心模块
- 知识点: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);
}
}