UFL4-3——事件中心传递自定义参数

事件中心中 参数类型自定义 指的是什么

上节课我们通过万物之父 object​ 利用里式替换原则(父类装子类),实现了在事件中心中传递参数
但是使用 object​ 时,当传递值类型参数时,存在装箱拆箱的性能浪费

而这节课要讲解的 参数类型自定义 指的就是 在传递数据时,
参数类型可以根据需求自己定义,想传什么就传什么,而不是固定的 object​ 类型

制作思路和具体实现

制作思路:
想让传递的参数类型可变,并且想传什么就传什么,我们可以很自然的联想到使用泛型UnityAction<T> ​来制作该功能

但是我们需要解决以下关键问题:字典容器中的 泛型委托 如何提前定好类型
我们不能直接让泛型类型的委托UnityAction<T>​作为Dictionary<TKey, TValue>​的值,因为不同泛型参数的委托不是一种类型的

而且我们也不能通过直接让EventCenter​变成泛型类的方法来解决这个问题,这样会破坏单例模式的唯一性

解决方案:里式替换原则(父类装子类)

声明一个EventInfo<T>​类,用来存储不同泛型参数的委托UnityAction<T>​,再让它继承一个不实现任何内容的抽象类EventInfoBase
这样EventInfoBase​即可装载不同泛型参数的EventInfo<T>​类,这样的父类可以作为Dictionary<TKey, TValue>​的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// 用于里氏替换原则的装载不同泛型类的EventInfo的父类
/// </summary>
public abstract class EventInfoBase { }

/// <summary>
/// 用来包裹对应观察者 函数委托的 类
/// </summary>
/// <typeparam name="T"></typeparam>
public class EventInfo<T> : EventInfoBase
{
//真正观察者 对应的 函数消息 记录在其中
public UnityAction<T> actions;

public EventInfo(UnityAction<T> action)
{
this.actions += action;
}
}

EventInfoBase​作为eventDic​的值,将触发事件,添加事件和移除事件方法改为泛型方法,
方法内通过as​获取对应的EventInfo<T>​,再通过actions​来调用其中的UnityAction<T>​,来执行委托,或者向委托添加或移除方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private Dictionary<string, EventInfoBase> eventDic = new Dictionary<string, EventInfoBase>();

// 触发事件
public void EventTrigger<T>(string eventName, T info)
{
//如果没有对象监听这个事件,则直接返回
if (!eventDic.ContainsKey(eventName))
return;
(eventDic[eventName] as EventInfo<T>).actions?.Invoke(info);
}

// 添加事件监听者
public void AddEventListener<T>(string eventName, UnityAction<T> action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo<T>).actions += action;
else
{
//eventDic.Add(eventName, new UnityAction(action)); //不要这样写,会无法移除传入的方法
eventDic.Add(eventName, new EventInfo<T>(action));
}
}

// 移除事件监听
public void RemoveEventListener<T>(string eventName, UnityAction<T> action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo<T>).actions -= action;
}

目前,我们写的方法,要求监听事件必须要带上一个参数,但监听事件不需要参数的情况也很多,因此我们需要添加一个无需参数的方法
声明一个不带泛型的EventInfo​,用来包裹 UnityAction​ ,同样继承EventInfoBase​,便于装载到eventDic​字典内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 用于里氏替换原则的装载不同泛型类的EventInfo的父类
/// </summary>
public abstract class EventInfoBase { }

/// <summary>
/// 用来记录无参无返回值的观察者函数委托的类
/// </summary>
public class EventInfo : EventInfoBase
{
public UnityAction actions;

public EventInfo(UnityAction action)
{
this.actions += action;
}
}

然后,在EventCenter​内声明对应的不需要泛型的触发无参事件方法,添加和移除无参监听事件方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private Dictionary<string, EventInfoBase> eventDic = new Dictionary<string, EventInfoBase>();

// 触发事件,不传递参数
public void EventTrigger(string eventName)
{
//如果没有对象监听这个事件,则直接返回
if (!eventDic.ContainsKey(eventName))
return;
(eventDic[eventName] as EventInfo).actions?.Invoke();
}

// 添加事件监听者,该监听方法不需要参数
public void AddEventListener(string eventName, UnityAction action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo).actions += action;
else
{
//eventDic.Add(eventName, new UnityAction(action)); //不要这样写,会无法移除传入的方法
eventDic.Add(eventName, new EventInfo(action));
}
}

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

使用示例

经过以上改造,我们即可让事件中心传递自定义类型的参数或者不传递参数,并且监听方法拿到的参数也不需要再转换类型了

但是要注意,一个事件名只能传递一种参数或者不传递参数,因此绝对不可以向一个事件名使用不同泛型类型的监听方法!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using UnityEngine;

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

public void Test()
{
print("无参事件监听者");
}

public void PlayerWaitMonsterDeadDo(Monster info)
{
Debug.Log("怪物名为:" + info.monsterName + ",怪物ID" + info.monsterID);
Debug.Log("玩家得到奖励");
}

public void OnDestroy()
{
EventCenter.Instance.RemoveEventListener<Monster>("MonsterDead", PlayerWaitMonsterDeadDo);
EventCenter.Instance.RemoveEventListener("Test", Test);
}
}

触发事件时,可以传入指定类型的对象或者不传入事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;

public class Monster : MonoBehaviour
{
public string monsterName = "123123123";
public int monsterID = 1;

public void Dead()
{
Debug.Log("怪物死亡了");
//其他对象在怪物死亡时要做的事情
EventCenter.Instance.EventTrigger<Monster>("MonsterDead", this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System;
using UnityEngine;

public class Main : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.X))
{
GameObject.Find("Monster").GetComponent<Monster>().Dead();
EventCenter.Instance.EventTrigger("Test");
}
}
}

按下X键输出:

image

可传递自定义类型的参数的事件中心具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 用于里氏替换原则的装载不同泛型类的EventInfo的父类
/// </summary>
public abstract class EventInfoBase { }

/// <summary>
/// 用来包裹对应观察者 函数委托的 类
/// </summary>
/// <typeparam name="T">参数类型</typeparam>
public class EventInfo<T> : EventInfoBase
{
//真正观察者 对应的 函数消息 记录在其中
public UnityAction<T> actions;

public EventInfo(UnityAction<T> action)
{
this.actions += action;
}
}

/// <summary>
/// 用来记录无参无返回值的观察者函数委托的类
/// </summary>
public class EventInfo : EventInfoBase
{
public UnityAction actions;

public EventInfo(UnityAction action)
{
this.actions += action;
}
}

/// <summary>
/// 事件中心模块
/// </summary>
public class EventCenter : BaseManager<EventCenter>
{
private Dictionary<string, EventInfoBase> eventDic = new Dictionary<string, EventInfoBase>();

private EventCenter() { }

/// <summary>
/// 触发事件,同时传递一个参数
/// </summary>
/// <typeparam name="T">要传递的参数的类型</typeparam>
/// <param name="eventName">要触发的事件名字</param>
/// <param name="info">要传递的参数</param>
public void EventTrigger<T>(string eventName, T info)
{
//如果没有对象监听这个事件,则直接返回
if (!eventDic.ContainsKey(eventName))
return;
(eventDic[eventName] as EventInfo<T>).actions?.Invoke(info);
}

/// <summary>
/// 触发事件,不传递参数
/// </summary>
/// <param name="eventName">要触发的事件名字</param>
public void EventTrigger(string eventName)
{
//如果没有对象监听这个事件,则直接返回
if (!eventDic.ContainsKey(eventName))
return;
(eventDic[eventName] as EventInfo).actions?.Invoke();
}

/// <summary>
/// 添加事件监听者,监听方法需要参数
/// </summary>
/// <typeparam name="T">监听方法的参数类型</typeparam>
/// <param name="eventName">要监听的事件</param>
/// <param name="action">监听到事件后要执行的方法</param>
public void AddEventListener<T>(string eventName, UnityAction<T> action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo<T>).actions += action;
else
{
//eventDic.Add(eventName, new UnityAction(action)); //不要这样写,会无法移除传入的方法
eventDic.Add(eventName, new EventInfo<T>(action));
}
}

/// <summary>
/// 添加事件监听者,该监听方法不需要参数
/// </summary>
/// <param name="eventName">要监听的事件</param>
/// <param name="action">监听到事件后要执行的方法</param>
public void AddEventListener(string eventName, UnityAction action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo).actions += action;
else
{
//eventDic.Add(eventName, new UnityAction(action)); //不要这样写,会无法移除传入的方法
eventDic.Add(eventName, new EventInfo(action));
}
}

/// <summary>
/// 移除带参数的事件监听
/// </summary>
/// <typeparam name="T">监听方法的参数类型</typeparam>
/// <param name="eventName">要移除监听的事件</param>
/// <param name="action">要移除监听的方法</param>
public void RemoveEventListener<T>(string eventName, UnityAction<T> action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo<T>).actions -= action;
}

/// <summary>
/// 移除无参事件监听
/// </summary>
/// <param name="eventName">要移除监听的事件</param>
/// <param name="action">要移除监听的方法</param>
public void RemoveEventListener(string eventName, UnityAction action)
{
if (eventDic.ContainsKey(eventName))
(eventDic[eventName] as EventInfo).actions -= action;
}

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

/// <summary>
/// 清除某一个事件的监听
/// </summary>
/// <param name="eventName"></param>
public void Clear(string eventName)
{
if (eventDic.ContainsKey(eventName))
eventDic.Remove(eventName);
}
}