多按钮联动开门:用 switch_device 数组做状态检查
「集齐三个机关才开门」是解谜地图的经典桥段,但直接拿毕业作品的 button_device 去做,会一头撞在墙上。这一页拆解一个论坛真实问答:问题出在哪、社区的解法为什么换了设备,以及背后那条通用的设计思路。本文整理自 Epic 开发者论坛与官方教程。
一、问题:按钮是「瞬时」的,存不住状态
论坛上开发者 N0baa 提问:想做「多个按钮都被激活后,门才打开」。听起来只是毕业作品的复数版,写起来却卡壳了——button_device 是瞬时触发的设备:玩家按下,InteractedWithEvent 闪一下就过去了,按钮本身不记得「我被按过」。它没有一个可以保持、可以事后查询的开/关状态。
于是「所有按钮都处于激活态」这个条件根本无处查询:事件是一条转瞬即逝的消息,不是一块可以随时看的状态牌。你当然可以在 Verse 里自己记账(后面会讲这条路),但社区答主 Manual Potato 给出的方案更省事:换一个天生会记账的设备。
二、社区方案:把状态搬进 switch_device
switch_device(开关设备)与按钮的最大区别是:开关是有状态的——扳上去就停在「开」,扳下来就停在「关」,随时可以查询当前处于哪一档。方案就水到渠成了:
- ▢ 把若干台 switch_device 放进一个
@editable数组插槽(官方 @editable 文档展示过同型的设备数组写法,如@editable Barriers:[]barrier_device = array{}); - ▢ 把若干台 switch_device 放进一个开了 Instance Editable 小眼睛的数组变量(官方文档展示过同型的设备数组写法,就是一个元素类型为设备的数组,面板里想加几台就加几台);
- ◇ 任何一个开关被扳动时,遍历数组,逐个查询状态;
- △ 全部为「开」才
Door.Disable()开门,否则按兵不动。
提问者确认此法解决了问题。思路骨架翻成代码是下面这样——注意两处 TODO:switch_device 不在本站的「已核实 API」清单里,它的扳动事件名与状态查询函数(GetCurrentState)的确切签名(是否需要 agent 参数、是否 <decides>),动手前请对照官方 API 页核实:
提问者确认此法解决了问题。思路骨架落成代码是下面这样——注意两处 TODO:switch_device 不在本站的「已核实 API」清单里,它的「被扳动」事件叫什么名字、查询开关档位的函数(GetCurrentState)到底怎么调(要不要传玩家引脚 agent、是不是一个「可能失败的」查询),动手前请对照官方 API 页核实:
读图翻译:Event BeginPlay 里先 Print 报个数,然后对每台开关 Bind「被扳动」事件到同一个自定义事件 OnToggled;OnToggled 触发时,拿一个计数器从 0 开始,用 ForEach 遍历所有开关、档位是「开」就 +1,最后用 Branch 判断「计到的数 = 开关总数」是否成立,成立就给 Barrier 连 Disable 节点开门。
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
multi_switch_door := class(creative_device):
# 所有开关收进一个数组插槽,面板里想加几个加几个
@editable
Switches:[]switch_device = array{}
@editable
Door:barrier_device = barrier_device{}
OnBegin<override>()<suspends>:void =
Print("联动门上线,共 {Switches.Length} 个开关。")
# 给每个开关订阅「被扳动」事件,套路同 button 的 Subscribe:
# for (Switch : Switches):
# Switch.TurnedOnEvent.Subscribe(OnToggled)
# TODO: 开关事件的确切名字与载荷以 Verse API Reference 为准
# 任一开关被扳动时调用:遍历数组,统计「开」的数量
OnToggled(Agent:agent):void =
var OnCount:int = 0
for (Switch : Switches):
if (Switch.GetCurrentState[]): # TODO: 以 Verse API Reference 为准
set OnCount += 1
if (OnCount = Switches.Length):
Door.Disable()
Print("所有开关就位,大门开启!")
骨架里全是老朋友:@editable 数组插槽、for (Switch : Switches): 遍历、failure context 里的 = 相等检定、.Length 数总数。真正的新东西只有一个观念:把状态存在设备里,代码只做汇总判断。每台开关自己记住自己的档位,Verse 这边不养任何 var,每次事件来了现场点名——状态没有第二份拷贝,也就永远不会和现场对不上。
骨架里全是老朋友:开了小眼睛的数组变量、for (Switch : Switches):(就是把 Switches 数组接进 ForEach 逐个跑)、Branch 式的「能不能走通」检定里用 = 比较是否相等、用 .Length 数总数。真正的新东西只有一个观念:把状态存在设备里,代码只做汇总判断。每台开关自己记住自己的档位,Verse 这边不新建任何变量,每次事件来了现场点名——状态没有第二份拷贝,也就永远不会和现场对不上。
三、另一条路:纯 Verse 记账,以及官方同型参考
如果关卡设计上就是要「按钮」的手感(按下弹回,没有扳杆),也可以走纯 Verse 记账路线:开一个 var PressedCount:int = 0,每个按钮的事件回调里计数,数够了开门。但这条路的坑比看起来多——同一个玩家反复按同一个按钮怎么办?你得再养一份「哪些按钮已被按过」的记录做去重,状态从一份变成两份,对账成本立刻上来了。两条路线的取舍很典型:设备存状态胜在无重复记账、面板可视;纯 Verse 记账胜在不受设备品种限制,逻辑再复杂也写得出来。
如果关卡设计上就是要「按钮」的手感(按下弹回,没有扳杆),也可以走纯 Verse 记账路线:新建一个整数变量 PressedCount,默认 0,每个按钮的事件回调(Bind 的那个自定义事件)里给它 +1,数够了开门。但这条路的坑比看起来多——同一个玩家反复按同一个按钮怎么办?你得再养一份「哪些按钮已被按过」的记录做去重,状态从一份变成两份,对账成本立刻上来了。两条路线的取舍很典型:设备存状态胜在无重复记账、面板可视;纯 Verse 记账胜在不受设备品种限制,逻辑再复杂也写得出来。
想看一个官方出品的同型实现,可以读 Escape Room 教程系列的第 10 篇:用 Verse 做的开关状态谜题(switch state puzzle),从摆设备到代码组织完整走了一遍,和本页的思路互为印证。
四、小测验
原帖里,为什么用 button_device 做不出「全部激活才开门」?
骨架代码里 if (OnCount = Switches.Length): 的 = 在干什么?
来源与延伸
本文整理自 Epic 开发者论坛与官方文档: