遊戲設計模式——Unity事件隊列(記念京阿尼事件)

「對消息或事件的發送與受理進行時間上的解耦。」html

 

在遊戲開發過程當中,常常會出現不一樣板塊之間的信息交流,或是存在「當...,就...」的狀況,事件隊列編程模式能夠有效解決消息傳遞中產生的腳本耦合問題,讓同一個板塊的腳本更加單純,不包含其餘腳本的雜質內容,使腳本更容易最大程度的複用。編程

事件隊列模式的運行流程以下:ide

1.當一個行爲(Action)觸發了某一事件(Event)後,不是直接調用該事件,而是改成申請將其提交給廣播中心,也就是將本身的行爲推入廣播材料的隊列末尾。函數

2.由中間的的廣播中心(事件隊列處理系統)統一管理播報這些行爲,而且按隊列順利前後播報,播報完了沒有廣播材料(隊列爲空)了就停下來摸魚。測試

3.關心這些行爲的聽衆會向廣播中心註冊一個偵聽器(買個收音機聽廣播中心的播報),聽到本身感興趣的,就自發執行相應事件。ui

4.哪一天這個聽衆煩了就把收音機砸了,這樣偵聽器就被移除了,之後不管再發生什麼都跟本身不要緊。this

 

因此,核心就是要創建這麼個廣播中心,這個廣播中心要能:spa

1.把稿子交過來(事件隊列入隊)code

2.廣播材料,例如很差啦京阿尼被燒了,播完後把稿子扔了(觸發事件,事件隊列出隊)htm

3.查看和管理收聽狀況,誰誰誰在聽啥(申請註冊,移除)

 

知道這些以後,就能夠來建造這麼一個廣播中心了,爲了提高人氣,誰均可以來一下,這個廣播中心須要接受各式各樣的爆料,因此要用到泛型委託;

並且這個廣播中心是全世界獨一無二的,不能有好幾個實例,你們都要從我這過,因此要用到單例;

關於單例,能夠看以前寫的博客:

http://www.javashuo.com/article/p-pseccouf-gs.html

 

  1 using System.Collections;
  2 using System.Collections.Generic;
  3 using UnityEngine;
  4 using UnityEngine.Events;
  5 using System;
  6 
  7 public class GameEvent{ }
  8 
  9 public class EventQueueSystem : MonoSingleton<EventQueueSystem>
 10 {
 11     public delegate void EventDelegate<T>(T e) where T : GameEvent;
 12 
 13     private delegate void InternalEventDelegate(GameEvent e);
 14 
 15     private Dictionary<Type, InternalEventDelegate> delegates = new Dictionary<Type, InternalEventDelegate>();
 16     private Dictionary<Delegate, InternalEventDelegate> delegateLookup = new Dictionary<Delegate, InternalEventDelegate>();
 17     private Dictionary<InternalEventDelegate, Delegate> delegateLookOnce = new Dictionary<InternalEventDelegate, Delegate>();
 18 
 19     private Queue eventQueue = new Queue();
 20 
 21     public bool bLimitQueueProcessing = false;
 22     public float limitQueueTime = 0.1f;
 23 
 24     //註冊偵聽事件(持續)
 25     public static void AddListener<T>(EventDelegate<T> del) where T : GameEvent
 26     {
 27         Instance.AddDelegate(del);
 28     }
 29 
 30     //註冊偵聽事件(一次)
 31     public static void AddListenerOnce<T>(EventDelegate<T> del) where T : GameEvent
 32     {
 33         var result = Instance.AddDelegate(del);
 34         if (result != null)
 35             Instance.delegateLookOnce[result] = del;
 36     }
 37 
 38     //斷定偵聽事件是否存在
 39     public static bool HasListener<T>(EventDelegate<T> del) where T : GameEvent
 40     {
 41         return Instance.delegateLookup.ContainsKey(del);
 42     }
 43 
 44     //移除偵聽事件
 45     public static void RemoveListener<T>(EventDelegate<T> del) where T : GameEvent
 46     {
 47         if (Instance == null)
 48             return;
 49         if (Instance.delegateLookup.TryGetValue(del, out InternalEventDelegate eventDelegate))
 50         {
 51             if (Instance.delegates.TryGetValue(typeof(T), out InternalEventDelegate temp))
 52             {
 53                 temp -= eventDelegate;
 54                 if (temp == null)
 55                     Instance.delegates.Remove(typeof(T));
 56                 else
 57                     Instance.delegates[typeof(T)] = temp;
 58             }
 59             Instance.delegateLookup.Remove(del);
 60         }
 61     }
 62 
 63     public static void RemoveAll()
 64     {
 65         if (Instance != null)
 66         {
 67             Instance.delegates.Clear();
 68             Instance.delegateLookup.Clear();
 69             Instance.delegateLookOnce.Clear();
 70         }
 71     }
 72 
 73     private InternalEventDelegate AddDelegate<T>(EventDelegate<T> del) where T : GameEvent
 74     {
 75         if (delegateLookup.ContainsKey(del))
 76             return null;
 77         void eventDelegate(GameEvent e) => del((T)e);
 78         delegateLookup[del] = eventDelegate;
 79 
 80         if (delegates.TryGetValue(typeof(T), out InternalEventDelegate temp))
 81             delegates[typeof(T)] = temp += eventDelegate;
 82         else
 83             delegates[typeof(T)] = eventDelegate;
 84         return eventDelegate;
 85     }
 86 
 87     //單個事件觸發
 88     private static void TriggerEvent(GameEvent e)
 89     {
 90         var type = e.GetType();
 91         if(Instance.delegates.TryGetValue(type,out InternalEventDelegate eventDelegate))
 92         {
 93             eventDelegate.Invoke(e);
 94             //移除單一偵聽
 95             foreach(InternalEventDelegate item in Instance.delegates[type].GetInvocationList())
 96             {
 97                 if (Instance.delegateLookOnce.TryGetValue(item,out Delegate temp))
 98                 {
 99                     Instance.delegates[type] -= item;
100                     if (Instance.delegates[type] == null)
101                         Instance.delegates.Remove(type);
102                     Instance.delegateLookup.Remove(temp);
103                     Instance.delegateLookOnce.Remove(item);
104                 }
105             }
106         }
107     }
108 
109     //外部調用的推入事件隊列接口
110     public static void QueueEvent(GameEvent e)
111     {
112         if (!Instance.delegates.ContainsKey(e.GetType()))
113             return;
114         Instance.eventQueue.Enqueue(e);
115     }
116 
117     //事件隊列觸發處理
118     void Update()
119     {
120         float timer = 0.0f;
121         while (eventQueue.Count > 0)
122         {
123             if (bLimitQueueProcessing)
124                 if (timer > limitQueueTime)
125                     return;
126             var e = eventQueue.Dequeue() as GameEvent;
127             TriggerEvent(e);
128             if (bLimitQueueProcessing)
129                 timer += Time.deltaTime;
130         }
131     }
132 
133     private void OnApplicationQuit()
134     {
135         RemoveAll();
136         eventQueue.Clear();
137     }
138 }

 

下面是用法測試:

1.例如日本有一個京阿黑怨念深重,這一天終於爆發,他準備燒京阿尼啦,因而:

 1 using UnityEngine;
 2 
 3 public class 京阿黑 : MonoBehaviour
 4 {
 5     private void Start()
 6     {
 7         EventQueueSystem.QueueEvent(new 燒京阿尼計劃("我要燒京阿尼啦!已備好兩桶共計80升汽油!"));
 8         //過了一段時間...
 9         EventQueueSystem.QueueEvent(new 燒成功啦("成功啦!京阿尼被我燒死了20+啦!"));
10     }
11 }

2.這是他準備爆料的稿子,以後廣播中心會按順利廣播這兩件事:

 1 public class 燒京阿尼計劃 : GameEvent
 2 {
 3     public string plan;
 4 
 5     public 燒京阿尼計劃(string plan)
 6     {
 7         this.plan = plan;
 8     }
 9 }
10 
11 public class 燒成功啦 : GameEvent
12 {
13     public string result;
14 
15     public 燒成功啦(string result)
16     {
17         this.result = result;
18     }
19 }

3.國外有個京阿粉早就關注京阿尼的消息了,天然這兩件事他也不能放過,一開始他就註冊了偵聽,而且已經作好了應對措施:

 1 using UnityEngine;
 2 public class 京阿粉 : MonoBehaviour
 3 {
 4     private void Awake()
 5     {
 6         EventQueueSystem.AddListener<燒成功啦>(知道結果後);
 7         EventQueueSystem.AddListener<燒京阿尼計劃>(聽了計劃後);
 8     }
 9 
10     private void 知道結果後(燒成功啦 e)
11     {
12         Debug.Log(e.result);
13         Debug.Log("完了,ACG業界完了...");
14     }
15 
16     private void OnDestroy()
17     {
18         EventQueueSystem.RemoveListener<燒京阿尼計劃>(聽了計劃後);
19         EventQueueSystem.RemoveListener<燒成功啦>(知道結果後);
20     }
21 
22     private void 聽了計劃後(燒京阿尼計劃 e)
23     {
24         Debug.Log(e.plan);
25         Debug.Log("什麼?!我要去救京阿尼!");
26     }
27 }

打印結果以下:

這裏有一點要注意,只有在京阿粉早就關注了這兩個事件時才能在第一時間作出反應,也就是說,註冊偵聽器的時間須要比事件發出的時間早才行,否則就沒有效果。

 

2019年12月2日更新:

今天在使用上面的事件系統時發現了一個不太方便的地方,例如我想在類A腳本中添加對某一事件E的偵聽,但想在另外一個腳本類B中去控制移除。

這時就有必要將事件E的委託函數記錄爲一個全局變量,而且該委託函數能夠在其餘腳本中全局取得。這樣一想以後,就很容易得出解決方案了。

只要將須要全局控制的委託變量統一放到一個單例類型的委託倉庫中就好了,能夠對該倉庫中的委託進行賦值或取值:

1 public class JoyStickUpEvent : GameEvent
2 {
3     public float TouchTime;
4     public JoyStickUpEvent(float touchTime)
5     {
6         TouchTime = touchTime;
7     }
8 }
1 public class EventDelegate:Singleton<EventDelegate>
2 {
3     public EventQueueSystem.EventDelegate<JoyStickUpEvent> JoyStickUpHandler;
4 //...其餘的全局委託
5 }

類A中設置委託值並添加偵聽:

1     private void JoyStickUpHandler(JoyStickUpEvent e)
2     {
3         //處理搖桿手柄擡起時的行爲
4     }
1     public override void OnAwake()
2     {
3         EventDelegate.Instance.JoyStickUpHandler = JoyStickUpHandler;
4         EventManager.AddListener(EventDelegate.Instance.JoyStickUpHandler);
5     }

類B中控制移除偵聽:

1     public override void Dead()
2     {
3         EventManager.RemoveListener(EventDelegate.Instance.JoyStickUpHandler);
4     }

 這樣一來,不管是事件的觸發仍是委託的全局修改都將變得更爲靈活和容易,甚至能夠在類A中對委託賦值,在類B中添加對應的事件偵聽,在類C中移除。

相關文章
相關標籤/搜索