自定义事件跨设备通信:论坛实战与 Dispatch 变体
第 21 课学会了 event(t) 的 Signal / Await;这一页深入论坛实战:声明报「抽象类」错误新建时报「抽象类」错误怎么破、Verse 事件为什么进不了原生设备的下拉框,以及当 Await 不够用时,社区怎么自制分发器。本文整理自 Epic 开发者论坛与官方社区教程。
一、声明的正确姿势:「类型 = 原型」缺一不可
论坛帖《How do I add custom events in verse creative devices?》里,楼主的第一次尝试是只写类型标注——直觉上很合理,却直接吃了编译错误:事件字段没有实例,编译器抱怨抽象类/抽象函数无法使用。正确写法必须让「类型标注」和「原型实例」成对出现:
论坛帖《How do I add custom events in verse creative devices?》里,楼主的第一次尝试是只写左半边(只声明它是个事件)——直觉上很合理,却直接 Compile 报红:这个事件没有初始值,引擎抱怨它是个抽象的、没法直接用的东西。正确写法必须让「左边的声明」和「右边的原型实例」成对出现:
# 错误:只有类型,没有实例 → 编译器报抽象类相关错误
# BadEvent<public>:event()
# 正确:「类型 = 原型」成对出现
GoEvent<public>:event() = event(){}
# 要捎带数据就给 event 加载荷类型
ScoreEvent<public>:event(int) = event(int){}
加上 <public> 是为了让别的设备访问得到——通信的另一端往往在另一个类里。用法与第 21 课一致:一端 GoEvent.Signal(),另一端 GoEvent.Await();带载荷的版本 ScoreEvent.Signal(99),等待方 NewScore := ScoreEvent.Await() 直接收到数字。Signal / Await 来自 /Verse.org/Concurrency(不同版本模板的 using 路径可能有差异,以 Verse API Reference 为准)。
加上 <public> 是为了让别的设备也访问得到——通信的另一端往往在另一台设备(另一个蓝图)里。用法与第 21 课一致:一端 GoEvent.Signal()(Call 一下分发器),另一端 GoEvent.Await()(Bind 上去等它响);想捎带数据就用带类型的版本 ScoreEvent.Signal(99),等的那一端 NewScore := ScoreEvent.Await() 直接收到数字 99。Signal / Await 来自 /Verse.org/Concurrency(不同版本模板的 using 路径可能有差异,以 Verse API Reference 为准)。
二、一个硬限制:Verse 事件进不了原生设备的下拉框
帖子里澄清了一个高频误解:很多人希望自定义事件像原生设备事件那样,出现在 Storm Controller、Trigger 这类原生创意设备的事件绑定下拉菜单里,用「设备直连」的方式消费它。答案是:不行。原生设备的下拉框只认原生事件;你在 Verse 里声明新建的 event(t),只能被其他 Verse 代码通过 Signal / Await / Subscribe 消费。
这条限制决定了架构方向:如果链路的两端都是 Verse 设备,自定义事件是最顺手的胶水;如果一端是原生设备,你就得反过来——让 Verse 去订阅原生设备的事件,或调用原生设备的函数,由 Verse 这一侧充当翻译官。
三、Dispatch 变体:当你想要「广播给一群设备」
Await 的消费模式是「谁等谁收到」,每个等待方都得自己挂起在事件上。帖中有用户展示了另一种思路:自制一个分发器(dispatcher)——内部维护一份回调(或监听者)清单,触发时逐个通知。官方社区教程《Use this to Subscribe to Events with Verse in UEFN》也演示了类似的订阅式封装。下面是思路的「监听设备数组」变体,只用第 21 课学过的语法:
Await 的模式是「谁等谁收到」,每个等待方都得自己停在事件上等。帖中有用户展示了另一种思路:自制一个分发器(dispatcher)——它其实很像蓝图里的事件分发器:内部存一份「监听者名单」,触发时挨个通知。官方社区教程《Use this to Subscribe to Events with Verse in UEFN》也演示了类似的订阅式封装。下面是这个思路的「监听设备数组」变体,只用第 21 课学过的写法:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
# 被通知方:暴露一个 public 方法当「收件口」
siren_device := class(creative_device):
OnNotified<public>():void =
Print("收到广播,警笛拉响!")
# 分发器:维护监听者清单,广播时逐个点名
notifier_device := class(creative_device):
# 在面板里把所有警笛设备拖进这个数组
@editable
Listeners:[]siren_device = array{}
Broadcast<public>():void =
for (L : Listeners):
L.OnNotified()
读图翻译:上半段 siren_device 暴露了一个 public 的 OnNotified 函数图当「收件口」。下半段 notifier_device 里那个 @editable 的 Listeners 是一个「装 siren 设备的数组」——进关卡后把所有警笛设备都拖进这个数组框。Broadcast 里用一个 ForEach 循环(for (L : Listeners))把数组里的每台警笛挨个取出来,运行它的 OnNotified——等于一次广播,全部点名。
论坛原帖的版本更进一步,存的不是设备引用而是回调函数数组,Dispatch() 时逐个调用——行为更接近原生设备事件的 Subscribe。函数类型的书写细节这里不展开,建议直接读原帖源码。
论坛原帖的版本更进一步,数组里存的不是设备引用,而是一串「函数」,Dispatch() 时逐个运行——行为更接近原生设备事件的 Subscribe(Bind)。这种「把函数当数据存」的写法细节这里不展开,建议直接读原帖源码。
四、三种设备间通信方式,怎么选?
| 方式 | 耦合度 | 多接收方 | 适合场景 |
|---|---|---|---|
@editable 引用直调 <public> 方法@editable 拖进来,直接调它开放的(<public>)函数 |
高(甲认识乙) | 手动拖数组 | 一对一、关系固定的设备对 |
共享 event(t) Signal / Await |
低(只认事件) | 所有 Await 方都被唤醒 | 一对多广播、跨模块解耦 |
| 自制 dispatcher | 中 | 清单里逐个通知 | 需要订阅式回调、可控通知顺序 |
经验法则:先试事件,事件是 Verse 并发模型的原生公民,和 race、spawn 无缝配合;只有当你明确需要「按清单顺序逐个通知」或「运行时增删监听者」时,才值得自己造 dispatcher。
GoEvent<public>:event() 只写这一行,为什么编译失败Compile 失败?
来源
本文整理自 Epic 开发者论坛与官方社区教程: