引言
一般的软件开发过程中,为了方便对项目进行管理、维护和扩展,通常会采用一种MVC框架,以将显示逻辑、业务逻辑和数据进行分离。
这在传统企业软件的开发中很常见,但我在使用Unity做游戏开发的时候却几乎找不到相关框架。
其原因猜测大概有两点,一是游戏开发模式多变,不同类型的游戏代码结构差异很大,很难有一个适用性很强的框架出现;二是Unity太年轻,其大范围使用也不过是最近三四年的事情。
没有框架也不是意味着没有办法,MVC只是一种规范,只要在开发过程中对代码的组织结构及用途做一定的约束,就能达到各层分离的效果。
在代码分层组织的结构中,出于解耦合的需求,通常需要一个对事件/消息进行管理的类,以便在各层之间传送消息,其功能包括事件/消息的订阅、发布以及取消订阅。
本文不会写怎么实现一个MVC结构(驾驭不了),只说说这个事件管理类的实现方法。
事件管理类的实现
1、编写EventManager类
一个事件管理类通过包括三个功能,订阅、发布消息、取消订阅,对应到代码中,也是就三个方法:AddEvent、DispatchEvent、RemoveEvent,还有一个字典List,对订阅事件做管理,实现如下:
1 /** 2 * UnityVersion: 2018.3.1f1
3 * FileName: EventManager.cs
4 * Author: TYQ
5 * CreateTime: 2019/04/04 15:49:53
6 * Description: 自定义的事件派发类
7 */
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using UnityEngine;
12
13 public class EventManager
14 {
15 /// <summary>
16 /// 带返回参数的回调列表,参数类型为T,支持一对多
17 /// </summary>
18 public static Dictionary<string, List<Delegate>> events = new Dictionary<string, List<Delegate>>();
19
20 /// <summary>
21 /// 注册事件,1个返回参数
22 /// </summary>
23 /// <param name="eventName"></param>
24 /// <param name="callback"></param>
25 public static void AddEvent<T> (string eventName, Action<T> callback)
26 {
27 List<Delegate> actions = null;
28
29 //eventName已存在
30 if (events.TryGetValue(eventName, out actions))
31 {
32 actions.Add(callback);
33 }
34 //eventName不存在
35 else
36 {
37 actions = new List<Delegate>();
38
39 actions.Add(callback);
40 events.Add(eventName ,actions);
41 }
42 }
43
44 /// <summary>
45 /// 注册事件,不带返回参数
46 /// </summary>
47 /// <param name="eventName"></param>
48 /// <param name="callback"></param>
49 public static void AddEvent(string eventName, Action callback)
50 {
51 List<Delegate> actions = null;
52
53 //eventName已存在
54 if (events.TryGetValue(eventName, out actions))
55 {
56 actions.Add(callback);
57 }
58 //eventName不存在
59 else
60 {
61 actions = new List<Delegate>();
62
63 actions.Add(callback);
64 events.Add(eventName, actions);
65 }
66 }
67
68 /// <summary>
69 /// 移除事件
70 /// </summary>
71 /// <param name="eventName"></param>
72 /// <param name="callback"></param>
73 public static void RemoveEvent<T>(string eventName, Action<T> callback)
74 {
75 List<Delegate> actions = null;
76
77 if (events.TryGetValue(eventName, out actions))
78 {
79 actions.Remove(callback);
80 if (actions.Count == 0)
81 {
82 events.Remove(eventName);
83 }
84 }
85 }
86 /// <summary>
87 /// 移除全部事件
88 /// </summary>
89 public static void RemoveAllEvents ()
90 {
91 events.Clear();
92 }
93
94 /// <summary>
95 /// 派发事件
96 /// </summary>
97 /// <param name="eventName"></param>
98 /// <param name="arg"></param>
99 public static void DispatchEvent<T>(string eventName, T arg)
100 {
101 List<Delegate> actions = null;
102
103 if (events.ContainsKey(eventName))
104 {
105 events.TryGetValue(eventName, out actions);
106
107 foreach (var act in actions)
108 {
109 act.DynamicInvoke(arg);
110 }
111 }
112 }
113 /// <summary>
114 /// 派发事件,不带参数
115 /// </summary>
116 /// <param name="eventName"></param>
117 /// <param name="arg"></param>
118 public static void DispatchEvent(string eventName)
119 {
120 List<Delegate> actions = null;
121
122 if (events.ContainsKey(eventName))
123 {
124 events.TryGetValue(eventName, out actions);
125
126 foreach (var act in actions)
127 {
128 act.DynamicInvoke();
129 }
130 }
131 }
132 }
EventManager.cs
2、测试事件类
2.1、先制作测试界面,包括两个接收(订阅)消息的Text组件,以及一个发布消息的Slider组件,层次结构见下图:
预期效果:拖动Slider,Slider的值会同步显示到两个用于接收的Text组件上。
2.2、编写测试类
先写一个发布消息的类,在Slider的onValueChanged事件中执行发布操作,如下
using System.Collections;using System.Collections.Generic;using UnityEngine;
using UnityEngine.UI;
public class Sender : MonoBehaviour
{
public Slider slider = null;
private void Awake()
{
slider.onValueChanged.AddListener(delegate (float value) {
Debug.LogFormat("slider:{0}", value);
//有参分发
EventManager.DispatchEvent<float>("NumberEvent", value);
//无参分发
EventManager.DispatchEvent("NumberEventNoParam");
});
}
}
Sender.cs
再写一下接收消息的类,,如下
using System;using System.Collections;using System.Collections.Generic;using UnityEngine;
using UnityEngine.UI;
public class Receiver : MonoBehaviour
{
public Text receiveText1 = null;
public Text receiveText2 = null;
private void Awake()
{
//带参数回调
//注册方法1
EventManager.AddEvent<float>("NumberEvent", OnNumberChangeEventHandler);
//注册方法2
EventManager.AddEvent("NumberEvent", delegate (float arg) {
receiveText2.text = (Convert.ToInt32(arg)).ToString();
});
//无参回调
EventManager.AddEvent("NumberEventNoParam", delegate () {
Debug.Log("无参回调");
});
}
/// <summary>
/// 事件处理方法
/// </summary>
/// <param name="arg"></param>
private void OnNumberChangeEventHandler (float arg)
{
receiveText1.text = (Convert.ToInt32(arg)).ToString();
}
}
Receiver.cs
2.3、运行
运行结果如下图:
可以看到,Slider值的改变会立马同步到接收端Text中,实现了预期的功能。
3、后记
1、我在EventManager.cs中使用Action类型来接受事件的回调,而不是使用c#的delegate,是因为,Action是Unity已经定义好的一种公共delegate,使用起来更方便。
2、目录的EventManager.cs只支持无参回调和一个参数的回调,如需更多参数回调,可以依照AddEvent<T>的写法,添加重载。
3、在EventManager.cs中好像还缺一个无参的RemoveEvent方法,请自行补充。
4、补充(2019-04-22)
实际使用中发现,派发消息时,传递两个参数和三个参数的情况还是挺多的,因此对EventManager进行了补充,有些不优雅的地方也进行了修改:
1 /** 2 * UnityVersion: 2018.3.1f1
3 * FileName: EventManager.cs
4 * Author: TYQ
5 * CreateTime: 2019/04/04 15:49:53
6 * Description: 自定义的事件派发类
7 */
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using UnityEngine;
12
13 public class EventManager
14 {
15 /// <summary>
16 /// 带返回参数的回调列表,参数类型为T,支持一对多
17 /// </summary>
18 public static Dictionary<string, List<Delegate>> events = new Dictionary<string, List<Delegate>>();
19
20 /// <summary>
21 /// 通用注册事件方法
22 /// </summary>
23 /// <param name="eventName"></param>
24 /// <param name="callback"></param>
25 private static void CommonAdd (string eventName, Delegate callback)
26 {
27 List<Delegate> actions = null;
28
29 //eventName已存在
30 if (events.TryGetValue(eventName, out actions))
31 {
32 actions.Add(callback);
33 }
34 //eventName不存在
35 else
36 {
37 actions = new List<Delegate>();
38
39 actions.Add(callback);
40 events.Add(eventName, actions);
41 }
42 }
43
44 /// <summary>
45 /// 注册事件,0个返回参数
46 /// </summary>
47 /// <param name="eventName"></param>
48 /// <param name="callback"></param>
49 public static void AddEvent(string eventName, Action callback)
50 {
51 CommonAdd(eventName, callback);
52 }
53
54 /// <summary>
55 /// 注册事件,1个返回参数
56 /// </summary>
57 /// <param name="eventName"></param>
58 /// <param name="callback"></param>
59 public static void AddEvent<T> (string eventName, Action<T> callback)
60 {
61 CommonAdd(eventName, callback);
62 }
63 /// <summary>
64 /// 注册事件,2个返回参数
65 /// </summary>
66 /// <param name="eventName"></param>
67 /// <param name="callback"></param>
68 public static void AddEvent<T, T1>(string eventName, Action<T, T1> callback)
69 {
70 CommonAdd(eventName, callback);
71 }
72 /// <summary>
73 /// 注册事件,3个返回参数
74 /// </summary>
75 /// <param name="eventName"></param>
76 /// <param name="callback"></param>
77 public static void AddEvent<T, T1, T2>(string eventName, Action<T, T1, T2> callback)
78 {
79 CommonAdd(eventName, callback);
80 }
81
82 /// <summary>
83 /// 通用移除事件的方法
84 /// </summary>
85 /// <param name="eventName"></param>
86 /// <param name="callback"></param>
87 private static void CommonRemove (string eventName, Delegate callback)
88 {
89 List<Delegate> actions = null;
90
91 if (events.TryGetValue(eventName, out actions))
92 {
93 actions.Remove(callback);
94 if (actions.Count == 0)
95 {
96 events.Remove(eventName);
97 }
98 }
99 }
100
101 /// <summary>
102 /// 移除事件 0参数
103 /// </summary>
104 /// <param name="eventName"></param>
105 /// <param name="callback"></param>
106 public static void RemoveEvent(string eventName, Action callback)
107 {
108 CommonRemove(eventName, callback);
109 }
110
111 /// <summary>
112 /// 移除事件 1个参数
113 /// </summary>
114 /// <param name="eventName"></param>
115 /// <param name="callback"></param>
116 public static void RemoveEvent<T>(string eventName, Action<T> callback)
117 {
118 CommonRemove(eventName, callback);
119 }
120
121 /// <summary>
122 /// 移除事件 2个参数
123 /// </summary>
124 /// <param name="eventName"></param>
125 /// <param name="callback"></param>
126 public static void RemoveEvent<T, T1>(string eventName, Action<T, T1> callback)
127 {
128 CommonRemove(eventName, callback);
129 }
130 /// <summary>
131 /// 移除事件 3个参数
132 /// </summary>
133 /// <param name="eventName"></param>
134 /// <param name="callback"></param>
135 public static void RemoveEvent<T, T1, T2>(string eventName, Action<T, T1, T2> callback)
136 {
137 CommonRemove(eventName, callback);
138 }
139
140 /// <summary>
141 /// 移除全部事件
142 /// </summary>
143 public static void RemoveAllEvents ()
144 {
145 events.Clear();
146 }
147
148 /// <summary>
149 /// 派发事件,0参数
150 /// </summary>
151 /// <param name="eventName"></param>
152 /// <param name="arg"></param>
153 public static void DispatchEvent(string eventName)
154 {
155 List<Delegate> actions = null;
156
157 if (events.ContainsKey(eventName))
158 {
159 events.TryGetValue(eventName, out actions);
160
161 foreach (var act in actions)
162 {
163 act.DynamicInvoke();
164 }
165 }
166 }
167
168 /// <summary>
169 /// 派发事件 1个参数
170 /// </summary>
171 /// <param name="eventName"></param>
172 /// <param name="arg"></param>
173 public static void DispatchEvent<T>(string eventName, T arg)
174 {
175 List<Delegate> actions = null;
176
177 if (events.ContainsKey(eventName))
178 {
179 events.TryGetValue(eventName, out actions);
180
181 foreach (var act in actions)
182 {
183 act.DynamicInvoke(arg);
184 }
185 }
186 }
187
188 /// <summary>
189 /// 派发事件 2个参数
190 /// </summary>
191 /// <param name="eventName">事件名</param>
192 /// <param name="arg">参数1</param>
193 /// <param name="arg2">参数2</param>
194 public static void DispatchEvent<T, T1>(string eventName, T arg, T1 arg2)
195 {
196 List<Delegate> actions = null;
197
198 if (events.ContainsKey(eventName))
199 {
200 events.TryGetValue(eventName, out actions);
201
202 foreach (var act in actions)
203 {
204 act.DynamicInvoke(arg, arg2);
205 }
206 }
207 }
208
209 /// <summary>
210 /// 派发事件 3个参数
211 /// </summary>
212 /// <param name="eventName">事件名</param>
213 /// <param name="arg">参数1</param>
214 /// <param name="arg2">参数2</param>
215 /// <param name="arg3">参数3</param>
216 public static void DispatchEvent<T1, T2, T3>(string eventName, T1 arg, T2 arg2, T3 arg3)
217 {
218 List<Delegate> actions = null;
219
220 if (events.ContainsKey(eventName))
221 {
222 events.TryGetValue(eventName, out actions);
223
224 foreach (var act in actions)
225 {
226 act.DynamicInvoke(arg, arg2, arg3);
227 }
228 }
229 }
230 }
EventManager