通常的軟件開發過程當中,爲了方便對項目進行管理、維護和擴展,一般會採用一種MVC框架,以將顯示邏輯、業務邏輯和數據進行分離。c#
這在傳統企業軟件的開發中很常見,但我在使用Unity作遊戲開發的時候卻幾乎找不到相關框架。框架
其緣由猜想大概有兩點,一是遊戲開發模式多變,不一樣類型的遊戲代碼結構差別很大,很難有一個適用性很強的框架出現;二是Unity太年輕,其大範圍使用也不過是最近三四年的事情。ide
沒有框架也不是意味着沒有辦法,MVC只是一種規範,只要在開發過程當中對代碼的組織結構及用途作必定的約束,就能達到各層分離的效果。測試
在代碼分層組織的結構中,出於解耦合的需求,一般須要一個對事件/消息進行管理的類,以便在各層之間傳送消息,其功能包括事件/消息的訂閱、發佈以及取消訂閱。spa
本文不會寫怎麼實現一個MVC結構(駕馭不了),只說說這個事件管理類的實現方法。3d
一、編寫EventManager類
一個事件管理類經過包括三個功能,訂閱、發佈消息、取消訂閱,對應到代碼中,也是就三個方法:AddEvent、DispatchEvent、RemoveEvent,還有一個字典List,對訂閱事件作管理,實現以下:code
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.一、先製做測試界面,包括兩個接收(訂閱)消息的Text組件,以及一個發佈消息的Slider組件,層次結構見下圖:orm
預期效果:拖動Slider,Slider的值會同步顯示到兩個用於接收的Text組件上。blog
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.三、運行
運行結果以下圖:
能夠看到,Slider值的改變會立馬同步到接收端Text中,實現了預期的功能。
一、我在EventManager.cs中使用Action類型來接受事件的回調,而不是使用c#的delegate,是由於,Action是Unity已經定義好的一種公共delegate,使用起來更方便。
二、目錄的EventManager.cs只支持無參回調和一個參數的回調,如需更多參數回調,能夠依照AddEvent<T>的寫法,添加劇載。
三、在EventManager.cs中好像還缺一個無參的RemoveEvent方法,請自行補充。
實際使用中發現,派發消息時,傳遞兩個參數和三個參數的狀況仍是挺多的,所以對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 }