事件管理器的由來
在游戲系統中,當一個對象需要去訪問另一個對象的時候,一般有幾種情況:
1、A對象是B對象的成員變量,B可以直接訪問A對象的公有函數。
2、A對象是一個單例,B對象可以通過A對象的單例進行訪問。
3、A對象注冊了回調到事件管理器中,B對象觸發事件管理器的事件。
這3種方式都能實現B對象調用A對象的函數,區別在于第1,2種方法A,B是直接耦合的,第三種方法A,B是沒有耦合關系的。事件管理器還能起到廣播的作用,例如當游戲發生掉線時,我們可以觸發掉線的事件,這樣所有注冊過掉線事件的對象都能收到通知,也就可以去處理掉線的邏輯,而不用每一個地方都去監測是否有掉線的情況。
事件的泛型注冊
當我們去注冊一個事件的時候,我們可能需要1個參數,2個參數或者多個參數,參數類型也不一樣,你可以通過傳入object的方式,在具體的回調里再去進行拆箱操作,還原成具體的參數,但是這樣就涉及到一個裝箱和拆箱的操作,不但效率不高還有GC的問題。泛型的參數設定可以一定程度上的去解決這個問題,但是參數的個數還是需要做適配的,例如你有1個,2個,3個,4個參數的回調函數,你就需要去分別取實現這幾個參數的Regsister,UnRegister和TriggerAction函數。如果函數參數太多,那么這個函數設計的可能有點問題,一般來說,4個參數足夠使用了。
代碼實現
public enum EventName
{
test1,
test2,
}
#region 0 param event
public void RegisterAction(EventName name,Action callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action func = action as Action;
if(func != null)
{
func += callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
_events[intName] = callback;
}
}
public void UnRegisterAction(EventName name,Action callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action func = action as Action;
if(func != null)
{
func -= callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventAlreadyUnRegsisteredException(name);
}
}
public void TriggerAction(EventName name)
{
int intName = (int)name;
Delegate callback = null;
_events.TryGetValue(intName,out callback);
if(callback != null)
{
Action func = callback as Action;
if(func != null)
{
func();
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventNotFindException(name);
}
}
#endregion
#region 1 param event
public void RegisterAction<T>(EventName name,Action<T> callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action<T> func = action as Action<T> ;
if(func != null)
{
func += callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
_events[intName] = callback;
}
}
public void UnRegisterAction<T>(EventName name,Action<T> callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action<T> func = action as Action<T>;
if(func != null)
{
func -= callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventAlreadyUnRegsisteredException(name);
}
}
public void TriggerAction<T>(EventName name,T arg1)
{
int intName = (int)name;
Delegate callback = null;
_events.TryGetValue(intName,out callback);
if(callback != null)
{
Action<T> func = callback as Action<T>;
if(func != null)
{
func(arg1);
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventNotFindException(name);
}
}
#endregion
#region 2 params event
public void RegisterAction<T,U>(EventName name,Action<T,U> callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action<T,U> func = action as Action<T,U> ;
if(func != null)
{
func += callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
_events[intName] = callback;
}
}
public void UnRegisterAction<T,U>(EventName name,Action<T,U> callback)
{
int intName = (int)name;
Delegate action = null;
_events.TryGetValue(intName,out action);
if(action != null)
{
Action<T,U> func = action as Action<T,U>;
if(func != null)
{
func -= callback;
_events[intName] = func;
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventAlreadyUnRegsisteredException(name);
}
}
public void TriggerAction<T,U>(EventName name,T arg1,U arg2)
{
int intName = (int)name;
Delegate callback = null;
_events.TryGetValue(intName,out callback);
if(callback != null)
{
Action<T,U> func = callback as Action<T,U>;
if(func != null)
{
func(arg1,arg2);
}
else
{
ThrowEventTranslationException(name);
}
}
else
{
ThrowEventNotFindException(name);
}
}
#endregion
寫在最后
為什么字典要用int作為健值呢,因為使用枚舉作為字典的健值時,枚舉類型沒有實現沒有實現IEquatable接口。因此,當我們使用Enum類型作為key值時,Dictionary的內部操作就需要將Enum類型轉換為System.Object,這就導致了Boxing的產生,會產生GC和效率問題,因此我們將字典的鍵值存為int,可以有效的避免這個問題。
工程地址:https://github.com/yrsc/EventSystem.git