- 按我的理解,抛开一些绕口的概念描述,事件系统可以描述为一种客户端代码模块内部的通讯机制。
- 当然,也可以用来双端联动。
游戏逻辑监听、抛出事件的机制。
Game Framework 中的很多模块在完成操作后都会抛出内置事件,监听这些事件将大大解除游戏逻辑之间的耦合。
除了 Game Framework 内置事件外,使用者也可以定义自己的游戏逻辑事件,游戏中所有事件均派生自 GameEventArgs 类,事件对象使用了引用池技术,以避免使用事件过程中频繁的内存分配。
E神原话
下面放出简化的代码结构--伪代码
EventManager
EventManager用来管理EventPool
public class EventManager : IEventManager
{
//事件池
private readonly EventPool m_EventPool;
///
/// 订阅事件处理函数。
///
/// 事件类型编号。
/// 要订阅的事件处理函数。
public void Subscribe(int id, EventHandler handler);
///
/// 取消订阅事件处理函数。
///
/// 事件类型编号。
/// 要取消订阅的事件处理函数。
public void Unsubscribe(int id, EventHandler handler);
///
/// 设置默认事件处理函数。
///
/// 要设置的默认事件处理函数。
public void SetDefaultHandler(EventHandler handler);
///
/// 抛出事件,这个操作是线程安全的,即使不在主线程中抛出,也可保证在主线程中回调事件处理函数,但事件会在抛出后的下一帧分发。
///
/// 事件源。
/// 事件参数。
public void Fire(object sender, GameEventArgs e);
///
/// 抛出事件立即模式,这个操作不是线程安全的,事件会立刻分发。
///
/// 事件源。
/// 事件参数。
public void FireNow(object sender, GameEventArgs e);
}
EventPool
EventPool用一个多值字典存储了事件,一个key可以对应多个事件
用队列缓存了事件参数,事件参数队列在引用池管理体系中,避免了反复生成事件参数实例带来的GC消耗
这个多值字典比较有意思,我们在下面看下具体实现
public class EventPool where T : BaseEventArgs
{
//多值字典 存放了所有已经注册的事件
private readonly GameFrameworkMultiDictionary> m_EventHandlers;
//事件参数队列 用来缓存EventArgs
private readonly Queue m_Events;
//事件池模式
private readonly EventPoolMode m_EventPoolMode;
//默认事件
private EventHandler m_DefaultHandler;
private sealed class Event : IReference
{
public object Sender;
public T EventArgs;
}
///.....EventManager中各种函数的具体实现逻辑
}
GF多值字典
下面一直到GF特色链表部分的简化代码特意留下了GF多值字典的运转的关键步骤
可以看到GF多值字典主体是:一个GF链表,一个字典
字典的value值用来存放GF链表中的两个节点,以此让key对应链表中的多个值(这些值在这两个节点之间)
下面给出逻辑构图
key和链表的对应关系
这是逻辑上的元素位置和对应关系,并不是物理空间上的元素位置关系,实际GF链表的各个节点可能是分散的存于内存中的。
同时E神也添加了GF多值字典和GF链表的foreach使用支持(这部分代码比较占地方就不展示了)
public class GameFrameworkMultiDictionary : IEnumerable>>, IEnumerable
{
//GF特色链表
private readonly GameFrameworkLinkedList m_LinkedList;
private readonly Dictionary> m_Dictionary;
///
/// 向指定的主键增加指定的值。
///
/// 指定的主键。
/// 指定的值。
public void Add(TKey key, TValue value)
{
GameFrameworkLinkedListRange range = default(GameFrameworkLinkedListRange);
if (m_Dictionary.TryGetValue(key, out range))
{
m_LinkedList.AddBefore(range.Terminal, value);
}
else
{
LinkedListNode first = m_LinkedList.AddLast(value);
LinkedListNode terminal = m_LinkedList.AddLast(default(TValue));
m_Dictionary.Add(key, new GameFrameworkLinkedListRange(first, terminal));
}
}
}
public struct GameFrameworkLinkedListRange : IEnumerable, IEnumerable
{
private readonly LinkedListNode m_First;
private readonly LinkedListNode m_Terminal;
}
GF特色链表
GF链表在C#自带的链表上包装了一层内存管理机制,减少了链表在释放和增加节点的时候可能会带来的GC
public class GameFrameworkLinkedList : ICollection, IEnumerable, ICollection, IEnumerable
{
private readonly LinkedList m_LinkedList;
private readonly Queue> m_CachedNodes;
///
/// 在链表中指定的现有结点前添加包含指定值的新结点。
///
/// 指定的现有结点。
/// 指定值。
/// 包含指定值的新结点。
public LinkedListNode AddBefore(LinkedListNode node, T value)
{
LinkedListNode newNode = AcquireNode(value);
m_LinkedList.AddBefore(node, newNode);
return newNode;
}
///
/// 如果有回收的缓存节点空间 则利用缓存空间 没有则申请新的空间
///
///
///
private LinkedListNode AcquireNode(T value)
{
LinkedListNode node = null;
if (m_CachedNodes.Count > 0)
{
node = m_CachedNodes.Dequeue();
node.Value = value;
}
else
{
node = new LinkedListNode(value);
}
return node;
}
///
/// 从链表中移除指定的结点。
///
/// 指定的结点。
public void Remove(LinkedListNode node)
{
m_LinkedList.Remove(node);
ReleaseNode(node);
}
private void ReleaseNode(LinkedListNode node)
{
node.Value = default(T);
m_CachedNodes.Enqueue(node);
}
}
(UGF)EventComponent
在游戏开发中使用GF的事件系统,几乎可以完全不依赖于unity,但是应该是出于统一标准的考量E神还是写了EventComponent,EventComponent几乎就是Eventmanager的函数搬运工这里就不贴代码了。
小结
GF的事件系统使用起来可能不会特别方便,但是在性能和内存的控制上已经做到了非常优秀的程度。
使用实例
public class TestEventArgs : GameEventArgs
{
///
/// 事件编号。
///
public static readonly int EventId = typeof(TestEventArgs).GetHashCode();
///
/// 获取事件编号
///
public override int Id
{
get
{
return EventId;
}
}
///
/// 输出字段
///
public string logString
{
get;
private set;
}
///
/// 创建事件
///
/// 内部事件。
/// 创建的事件。
public static TestEventArgs Create(string logString)
{
// 使用引用池技术,避免频繁内存分配
TestEventArgs e = ReferencePool.Acquire();
e.logString = logString;
return e;
}
public override void Clear()
{
logString = null;
}
}
namespace StarForce
{
public class TestNode : MonoBehaviour
{
EventHandler Logstring;
private void Awake()
{
Logstring += LogString;
}
private void Start()
{
///订阅事件
GameEntry.Event.Subscribe(TestEventArgs.EventId, Logstring);
///发送事件
GameEntry.Event.Fire(this, TestEventArgs.Create("猫刀刀"));
}
private void LogString(object sender, GameEventArgs e)
{
TestEventArgs ne = (TestEventArgs)e;
Debug.LogError(ne.logString);
}
}
}
运行结果