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