用Unity做个游戏(二) - 事件系统
前言
之前一直在用cocos2d-x用c++写代码嘛,所以为了开发方便自己设计了一套事件系统,现在转到Unity用c#了,就很自然地把之前那套东西搬过来用了XD。
C#自带的event其实就完全可以用,不过功能略显简陋,Unity自带的EventSystem感觉太复杂了,而且我又不太喜欢去操作editor,所以我还是决定自己造轮子,弄一个最适合自己的事件机制。
设计
事件机制基于观察者模式,事件订阅者向某个事件发布者订阅事件,绑定回调函数,事件发布者发布事件时会通知所有订阅者,按订阅顺序依次调用他们预留的回调函数
三个类,SFEvent
(SF是前缀,啥意思大家也不用在意233),SFEventData
,SFEventDispatcher
| 类 | 作用 |
| ----------------- | -------- |
| SFEvent | 事件类 |
| SFEventData | 事件所包含的数据 |
| SFEventDispatcher | 事件发布者 |
SFEvent
其中,事件类SFEvent
包含类型,事件数据SFEventData
,以及发布者
不同事件的数据可能需要不同的数据结构,所以我们抽象出一层接口ISFEventData
,实际使用的数据结构均继承这个接口即可
下面贴出部分代码(不完整,完整代码见本文末尾)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class SFEvent { public string eventType; // 事件类型 public ISFEventData data; // 事件包含的自定义数据 public object target; // 事件发送者 // 以下是事件枚举 public const string EVENT_TEST = "EVENT_TEST" ; }; public interface ISFEventData { }; |
SFEventData
所有事件数据的数据结构均继承了ISFEventData
这个空接口,这样一来不同的事件就可以定制自己需要的事件数据结构了,如果时间数据非常简单比如只有一个数字,我这里也准备了一个通用的数据结构:
1 2 3 4 5 6 7 | public class SFSimpleEventData : ISFEventData { public object objVal = null ; public int intVal = 0; public float floatVal = 0; public string strVal = "" ; } |
SFEventDispatcher
这个事件系统的核心部分,负责分发事件,管理订阅
之前用cocos的时候习惯使用多继承的方式来把dispatcher的功能加到一个类上,不过现在C#不支持多继承了,于是就只能使用组合的方式,在类中储存一个dispatcher类的实例来实现类似的效果。写法会从this->dispatchEvent(e);
变成this.dispatcher.dispatchEvent(e);
使用C#的委托来实现订阅,订阅者传递这个委托类型的回调函数给发布者即可:
1 | public delegate void SFListenerSelector(SFEvent e); |
使用一个字典来管理不同的事件所各自对应的订阅者
1 | Dictionary< string , list |
订阅者通过addEventListener
方法来订阅一个事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public bool addEventListener( string eventType, SFListenerSelector sel) { if (eventType != "" && sel != null ) // 判断有效性 { if (hasEventListener(eventType, sel)) { // SFUtils这个类是我自己弄的,下一篇文章再讲 SFUtils.log( string .Format( "重复监听!type={0}" , eventType)); } if (!m_dictListeners.ContainsKey(eventType)) { // 不存在的话就新建一个 List newSelectors = new List(); m_dictListeners[eventType] = newSelectors; } var selectors = m_dictListeners[eventType]; selectors.Add(sel); return true ; } return false ; } |
任何人都可以通过发布者(通常来说是发布者自己)来发布事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public int dispatchEvent(SFEvent e) { int count = 0; if (m_dictListeners.ContainsKey(e.eventType)) { var selectors = m_dictListeners[e.eventType]; foreach (var item in selectors) { item(e); count += 1; } } return count; } |
用途相关
其实更常见的使用场景是在UI,我这次用的是Unity5自带的UGUI,写了一个控件来结合Unity的事件系统和我这个事件系统,实际开发的时候使用UI事件的方式和使用普通事件的方式是完全一致的,这个我们后面再详细介绍。