问号的两种身份:?type 选项类型与 ?Name 可选参数
第 9 课里你已经认识了后缀 ? 查询运算符,但问号在 Verse 里还打着两份工:站在类型前面表示「可能为空」,站在参数前面表示「可以不传」。这一页把问号的三张面孔摆在一起,以后见到它再也不迷路。
一、论坛现场:?creative_prop 是个啥
Epic 开发者论坛上有一个非常典型的提问:一位开发者调用生成道具的 SpawnProp 时,发现返回值类型写着 ?creative_prop——名字前凭空多了个问号,这是什么类型?Epic 员工 Kurtis Schmidt 的回答一锤定音:问号出现在类型位置,表示这是一个 option(选项)类型——一个「可以装着一个值,也可以什么都不装」的容器,相当于其他语言里的可空类型(nullable)。
Epic 开发者论坛上有一个非常典型的提问:一位开发者用生成道具的 SpawnProp 时,发现它交回来的结果类型写着 ?creative_prop——名字前凭空多了个问号,这是什么类型?Epic 员工 Kurtis Schmidt 的回答一锤定音:问号出现在类型位置,表示这是一个 option(选项)类型——一个「可以装着一个值,也可以什么都不装」的盒子,就是蓝图里那种「可能有效、也可能是空的」引用/变量。
为什么 SpawnProp 要这样设计?因为生成道具这件事本身可能不成立:位置不合法、资源不对……API 与其在失败时塞给你一个残缺对象,不如用 ?creative_prop 把话挑明:「你拿到的可能是道具,也可能是空盒子,拆开之前请先检查。」这正是 Verse 用类型系统逼你处理失败的又一处体现(SpawnProp 的确切签名以 Verse API Reference 为准)。
为什么 SpawnProp 要这样设计?因为生成道具这件事本身可能不成立:位置不合法、资源不对……与其在失败时塞给你一个空的、一碰就报 Accessed None 的引用,不如用 ?creative_prop 把话挑明:「你拿到的可能是道具,也可能是空盒子,拆开之前请先检查。」这正是 Verse 从源头逼你先检查、再使用的又一处体现(SpawnProp 的确切签名以 Verse API Reference 为准)。
二、身份一:?type——可能有货的盒子
在类型前面加 ?,就得到该类型的 option 版本:?int 是「可能装着 int 的盒子」。空盒子写作 false,装货用 option{值},拆箱则交给你在第 9 课认识的老朋友——后缀 ? 查询运算符,在失败上下文里进行:
在类型前面加 ?,就得到该类型的 option 版本:?int 是「可能装着一个整数的盒子」。空盒子写作 false,装货用 option{值},拆箱则交给你在第 9 课认识的老朋友——后缀 ?,放在 Branch 这类判断位置里做:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
mystery_box_device := class(creative_device):
# 类型位置的 ?:选项类型——盒子里可能有 int,也可能是空的
var MaybePrize:?int = false
OnBegin<override>()<suspends>:void =
set MaybePrize = option{500}
# 表达式位置的后缀 ?:拆箱,失败上下文里进行
if (Prize := MaybePrize?):
Print("开箱!奖金 {Prize} 金币")
else:
Print("盒子是空的")
读图翻译:先在变量面板建一个 MaybePrize,类型是「可能装整数的盒子」,初始为空(false)。BeginPlay 里先用一个 Set 往盒子里放 500,再用一个 Branch 拆盒子:MaybePrize? 拆得出来(非空)就把值命名成 Prize、走成功引脚 Print 奖金,拆不出就走 else Print「空的」。
注意这两个问号的分工:?int 里的问号是类型标注,描述盒子的款式;MaybePrize? 里的问号是查询运算符,是一次可失败的动作。写法只差一个位置,身份完全不同。
注意这两个问号的分工:?int 里的问号是类型标注,描述盒子的款式;MaybePrize? 里的问号是拆盒子的动作,是一次可能成功、也可能走不通的操作。写法只差一个位置,身份完全不同。
三、身份二:?Name——可以不传的参数
Kurtis Schmidt 在同一个回答里还补充了问号的第二种用法:写在函数参数名前面,表示这是一个可选命名参数,可以带默认值。调用者既可以省略它(用默认值),也可以点名传入:
Kurtis Schmidt 在同一个回答里还补充了问号的第二种用法:写在蓝图函数的输入参数名前面,表示这是一个可选命名参数,可以带默认值——有点像蓝图节点上某个输入引脚你不连线,它就用引脚里填好的默认值。调用时既可以省略它,也可以点名传入:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
cheer_device := class(creative_device):
# 参数位置的 ?:可选命名参数,不传就用默认值 1
Cheer(?Times:int = 1):void =
Print("加油 x {Times}!")
OnBegin<override>()<suspends>:void =
Cheer() # 省略参数,Times = 1
Cheer(?Times := 3) # 点名传参,Times = 3
读图翻译:Cheer 是一个自定义蓝图函数,有一个可选输入 Times,默认填 1。BeginPlay 里调用了两次:第一次 Cheer() 不填,就用默认的 1;第二次 Cheer(?Times := 3) 点名把这个输入设成 3。
把三张面孔排成一张速查表,以后见到问号先看它站的位置:
| 站位 | 长相 | 含义 |
|---|---|---|
| 表达式后缀 | HasKey?、MaybePrize? |
查询 logic / 拆箱 option,可失败,要待在失败上下文里查询 logic(Boolean)/ 拆箱 option,可能走不通,要待在 Branch 这类判断位置里 |
| 类型前缀 | ?int、?creative_prop |
选项类型:可能有值,也可能为空 |
| 参数名前缀 | ?Times:int = 1 |
可选命名参数,调用时可省略 |
四、随堂小测 ★
var Slot:?agent = false 这行代码里,类型前的 ? 表示什么?
本文整理自 Epic 开发者论坛问答,Epic 员工 Kurtis Schmidt 的回答见原帖:Types prefixed by a "?" — Unreal Engine Forums ↗