UGF学习2-事件
https://gameframework.cn/document/event/
事件是游戏逻辑监听、抛出事件的机制。
Game Framework 中的很多模块在完成操作后都会抛出内置事件,监听这些事件将大大解除游戏逻辑之间的耦合。
除了 Game Framework 内置事件外,使用者也可以定义自己的游戏逻辑事件,游戏中所有事件均派生自 GameEventArgs 类,事件对象使用了引用池技术,以避免使用事件过程中频繁的内存分配。
常用功能
获取事件组件
EventComponent eventComponent = GameEntry.GetComponent<EventComponent>();
订阅事件
void Subscribe(int eventID, function);
- eventID
事件ID - function
事件响应函数
- eventID
取消订阅事件
void Unsubscribe(int eventID, function);
检查是否订阅事件
bool Check(int eventID, function);
广播事件
void Fire(object sender, GameEventArgs e);
可保证线程安全,即使不在主线程中抛出,也可保证在主线程中回调事件处理函数,但事件会在抛出后的下一帧分发。- sender:
广播发起人(通常为this) - e:
进行广播的事件
- sender:
void FireNow (object sender, GameEventArgs e)
立即广播,线程不安全
获取某个事件的事件处理函数数量
int Count(int EventId);
获取待处理事件数量
EventCount
这是 EventComponent 的属性
事件响应函数的实现
实现方法如下:
1 |
|
其中参数为:
- object sender
事件发送者 - GameEventArgs e
事件
案例:
1 |
|
设置默认事件响应函数
定义并设置默认事件处理函数,当任意事件被抛出且不存在任何事件处理函数时,默认事件处理函数将被调用。
void SetDefaultHandler(function);
自定义事件
自定义事件需要继承 GameEventArgs
案例如下:
自定义事件类:
1 |
|
自定义事件处理函数:
1 |
|
订阅自定义事件:
1 |
|
广播自定义事件:
1 |
|
常见问题
提取 EventHandler 降低内存分配
由于将方法转换为 EventHandler 时会有内存分配,所以建议将 EventHandler 预定义为临时变量甚至所在类的成员变量,来降低内存开销。
1 |
|
事件订阅与取消订阅不匹配
事件的订阅与取消订阅在使用生命周期内,应当成对出现。比如实体显示时订阅、隐藏时取消订阅,界面打开时订阅、关闭时取消订阅。
Game Framework 会严格检查事件订阅的匹配情况,不允许出现重复订阅,也不允许出现重复取消订阅或取消订阅尚未订阅的事件处理函数,如果出现这些情况,将会抛出异常。
有时,使用者在对象生命期结束前,的确写有取消订阅的代码,但非预期的逻辑导致逻辑块提前跳出,取消订阅的代码最终未被调用,进而下一次订阅时出现重复订阅。
错误地缓存了事件实例
事件处理函数处理事件时,不应该缓存 GameEventArgs 实例,GameEventArgs 实例的有效生命周期,仅限于事件处理函数内。
Game Framework 为了降低内存分配,事件实例使用了引用池技术。一个事件被其所有事件处理函数处理完成后,事件实例会被立刻清理并回收(调用 GameEventArgs 中的 Clear 方法)。后续逻辑访问缓存的事件实例时,将无法访问到任何有效数据,甚至访问到此实例被复用后的数据。
因此,缓存事件实例是完全错误的,这将导致难以追踪的 BUG。
1 |
|
自定义事件类的 Clear 方法实现不完整
自定义事件类使用了引用池技术,事件实例是会被复用的。当回收某个事件实例时,不完整的 Clear 实现无法清理干净事件实例中的数据,进而将脏数据带到下一次复用事件实例的逻辑中去,这可能导致难以追踪的 BUG。
手误导致的事件处理函数不生效
订阅成功事件时,同时订阅相匹配的失败事件是一个好习惯,但有时复制粘贴代码会惹祸,如以下示例。
1 |
|
事件模块不工作
事件模块需要被轮询才能正常调用事件处理函数,请确认正确初始化了 Game Framework。