Unity3D中利用Action实现自己的消息管理(订阅/发布)类
引言
一般的软件开发过程中,为了方便对项目进行管理、维护和扩展,通常会采用一种MVC框架,以将显示逻辑、业务逻辑和数据进行分离。
这在传统企业软件的开发中很常见,但我在使用Unity做游戏开发的时候却几乎找不到相关框架。
其原因猜测大概有两点,一是游戏开发模式多变,不同类型的游戏代码结构差异很大,很难有一个适用性很强的框架出现;二是Unity太年轻,其大范围使用也不过是最近三四年的事情。
没有框架也不是意味着没有办法,MVC只是一种规范,只要在开发过程中对代码的组织结构及用途做一定的约束,就能达到各层分离的效果。
在代码分层组织的结构中,出于解耦合的需求,通常需要一个对事件/消息进行管理的类,以便在各层之间传送消息,其功能包括事件/消息的订阅、发布以及取消订阅。
本文不会写怎么实现一个MVC结构(驾驭不了),只说说这个事件管理类的实现方法。
事件管理类的实现
1、编写EventManager类
一个事件管理类通过包括三个功能,订阅、发布消息、取消订阅,对应到代码中,也是就三个方法:AddEvent、DispatchEvent、RemoveEvent,还有一个字典List,对订阅事件做管理,实现如下:
EventManager.cs1 /** 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 }
2、测试事件类
2.1、先制作测试界面,包括两个接收(订阅)消息的Text组件,以及一个发布消息的Slider组件,层次结构见下图:
预期效果:拖动Slider,Slider的值会同步显示到两个用于接收的Text组件上。
2.2、编写测试类
先写一个发布消息的类,在Slider的onValueChanged事件中执行发布操作,如下
Sender.csusing 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"); }); } }再写一下接收消息的类,,如下
Receiver.csusing 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(); } }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进行了补充,有些不优雅的地方也进行了修改:
EventManager1 /** 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 }