Verse Wiki — 写给 UE6 时代的 Verse 互动手册
拓展 · EXTRA

链式可失败绑定:设备代码的标准开场

几乎每个 UEFN 设备脚本都以同一段舞步开场:从 agent 拿到 player,再拿到 fort_character,再取角色的状态——每一步都可能失败。这一页教你把整串检定优雅地堆进一个失败上下文,从此告别层层判空的金字塔。

一、每个设备脚本的开场舞步

在 UE6 里写玩法逻辑,事件回调递给你的往往是一个 agent——它可能是玩家,也可能不是;就算是玩家,他此刻可能没有场上的角色实体(比如正处于淘汰观战状态)。于是「拿到能干活的对象」这件事,天然是一条每一环都可能断的链:agent → player[Agent](类型转换,可能失败)→ Player.GetFortCharacter[](取场上角色,可能失败)→ 再往下取血量、位置……

在 UE6 里写玩法逻辑,一个事件触发时递给你的往往是一个 agent——相当于蓝图事件里那个「玩家 / Instigator」引脚。它可能是玩家,也可能不是;就算是玩家,这一刻他也未必有场上的角色实体(比如正被淘汰、在观战)。于是「拿到一个能干活的对象」这件事,天生就是一条每一环都可能断的链:agent →(Cast 成 player,可能失败)→ 从 player 取场上角色 GetFortCharacter[](也可能失败)→ 再往下取血量、位置……在蓝图里,这一串就是一个接一个的 Cast 与取值节点。

在没有失败上下文的语言里,这种链条的下场大家都见过:一层 if 判空套一层 if 判空,缩进不断右移,人称「末日金字塔」。而 Verse 的答案正是第 8 课的两件套:逗号即 and + 条件内联绑定——把整条链堆进同一个失败上下文,一次检定定乾坤:

在没有失败上下文的语言(或者纯靠蓝图连线)里,这种链条的下场大家都见过:一个 IsValid 判空套着下一个判空,节点一层层往右缩,人称「末日金字塔」。而 Verse 的答案正是第 8 课的两件套:逗号即 and + 条件里内联绑定——把整条链堆进同一个失败上下文,一次检定定乾坤:

greeting_device.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

greeting_device := class(creative_device):

    @editable
    TriggerButton:button_device = button_device{}

    OnBegin<override>()<suspends>:void =
        TriggerButton.InteractedWithEvent.Subscribe(OnPressed)

    OnPressed(Agent:agent):void =
        # 两连检定:agent → player → fort_character
        # TODO: 以 Verse API Reference 为准
        if (Player := player[Agent], Fort := Player.GetFortCharacter[]):
            Print("检定全过,拿到了场上的角色实体。")
        else:
            Print("链条断在某一环,但我们安全地走到了这里。")

先扫一眼整段:@editableTriggerButton 就是一个「Instance Editable」的按钮引用——把这个设备拖进关卡后,在 Details 面板里指给它一个真按钮。OnBegin(相当于 Event BeginPlay)里那句 Subscribe,等于蓝图里的「Bind Event」:把按钮的「被交互」事件绑到你自己的 OnPressed 上。之后每次有人按下按钮,OnPressed 就被调用一次,参数 Agent 就是那个按按钮的玩家引脚。真正的重头戏在 OnPressed 里那一行 if——下面细读。

读一遍这个条件:player[Agent] 是方括号的类型转换——Agent 真是玩家才成功;成功的值当场绑定给 Player,紧接着下一个条件就能用它,调用 Player.GetFortCharacter[] 再掷一次骰。任何一环失败,整个条件失败,统一走 else;全过,then 分支里 PlayerFort 都已就位。没有嵌套,没有判空,没有金字塔。

读一遍这个条件:player[Agent] 是一次用方括号写的 Cast——把 Agent 试着转成 player,真是玩家才成功;转成功的值当场存进 Player,紧接着下一个条件就能直接用它,拿 Player.GetFortCharacter[] 再掷一次骰(相当于再取一次场上角色,取不到就失败)。任何一环失败,整个条件就失败,统一走 else;全过,then 那条线里 PlayerFort 都已就位。没有层层嵌套,没有一个个 IsValid 判空,没有金字塔。

二、横着写与竖着写:逗号版与 if: 多行版

链条只有两三环时,单行逗号最顺手。环一多(或者每环都想加注释),Verse 还提供竖排形态:if: 之后每行一个条件,then: 接主体,else: 接失败分支——语义与逗号版完全一致,前面的绑定照样供后面使用:

链条只有两三环时,单行逗号最顺手。环一多(或者每一环都想写句注释),Verse 还给了一副竖排写法:if: 之后每行列一个条件,then: 接主体,else: 接失败那条线——效果和逗号版完全一样,前面存下的名字照样供后面用:

vertical_chain.verse
OnPressed(Agent:agent):void =
    if:
        # 第一环:这个 agent 是玩家吗?
        Player := player[Agent]
        # 第二环:玩家现在有场上角色吗?
        Fort := Player.GetFortCharacter[]
    then:
        Print("全部成功,开始干活。")
    else:
        Print("任何一环失败都会到这里。")

两种形态怎么选,社区的共识很朴素:两三环、不需要逐环注释,用逗号;更长的链、或者每一环都值得写一句「为什么」,用竖排。切记第 8 课的作用域规则在这里同样生效:PlayerFort 只在后续条件与 then 分支可见,else 分支两眼一抹黑——走到 else 时你甚至不知道链条断在第几环。真需要区分断点时,把链拆成多个 if 即可。

两种写法怎么选,社区的共识很朴素:两三环、不用逐环写注释,就用逗号;链更长、或者每一环都值得写句「为什么」,就用竖排。还要记牢第 8 课那条规矩:PlayerFort 这些临时名字,只有后面的条件和 then 那条线看得见,else 那条线两眼一抹黑——走到 else 时,你甚至不知道链子到底断在第几环。真要分清断点,把链拆成好几个 if 分开写就行。

另外提醒一句工程习惯:本页的 GetFortCharacter 等成员的确切签名,以你项目里的 Verse API Reference 与 digest 文件为准——API 细节随版本演进,但「链式检定」这个模式本身是稳定的。

三、为什么这比层层判空香

对比一下其他语言里同一件事的常见写法(伪代码):

pyramid.pseudo(某些其他语言)
if (agent != null) {
    var player = agent.AsPlayer();
    if (player != null) {
        var character = player.GetCharacter();
        if (character != null) {
            // 三层缩进之后,终于开始干正事
        }
    }
}

金字塔写法有两个老毛病:一是纵深,正事被推到三四层缩进之外;二是漏判,只要有一层忘了判空,运行时就是一记空引用崩溃。Verse 的链式检定把两个毛病一起治了:缩进恒定一层;而「忘了判空」这种事故在语言层面不可能发生——可失败表达式必须待在失败上下文里,编译器逼着你交代失败去向。第 8 课说「失败是一等公民」,这一页就是它在日常工程里最实惠的回报:安全是默认值,而不是自觉性

金字塔写法有两个老毛病:一是纵深,正事被挤到三四层缩进之外;二是漏判,只要有一层忘了做 IsValid 判空,运行时就是一记「Accessed None」——空引用崩溃。Verse 的链式检定把这两个毛病一起治了:缩进永远只有一层;而「忘了判空」这种事故在语言层面根本发生不了——可失败表达式必须待在失败上下文里,编译器逼着你当场交代「失败了走哪」。第 8 课说「失败是一等公民」,这一页就是它在日常干活时最实惠的回报:安全是默认值,而不是靠你自觉

if (A := Foo[], B := A.Bar[]): 中,如果 A.Bar[] 这一环失败了,会发生什么?

四、来源与延伸

本文整理自官方文档与社区教程,模式本身在官方 if 文档中即有示范:

If in Verse — Epic 官方文档 ↗

UEFN Verse Programming Tutorial — Generalist Programmer(社区教程)↗

把这段开场舞步练进肌肉记忆吧——从下一个设备开始,你的每个事件回调大概率都以它开头。