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

message 与 <localizes>:UI 文本为什么不是 string

你刚学会了 string,信心满满地去给告示牌设置文字——结果编译器冷冷地说:这里要的是 message,不是 string。这堵墙几乎每个创作者都撞过。这一页讲清 message 是什么、为什么存在,以及从 string 过去的唯一正道。

一、撞墙现场:SetText 拒收 string

在 UE6 里做 UI 时——告示牌、text_block 文本控件、各种带 SetText 的设备——你会发现这些 API 的参数类型写的是 message,而不是 string。更让人措手不及的是:两者之间没有直接转换。没有 ToMessage(),没有强制转换语法,把 string 硬塞过去只会收获一条类型不匹配的编译错误。

在 UE6 里做 UI 时——告示牌、text_block 文本控件、各种带 SetText 的设备——你会发现这些设备节点的输入引脚要的是 message,而不是 string。更让人措手不及的是:两者之间没有直接转换。没有 ToMessage() 节点,也没有一根线能把 string 直接连进 message 引脚,硬连只会让引脚类型对不上、一点 Compile 就报红。

Epic 开发者论坛上,「怎么把暴露在面板上的 @editable(即蓝图里勾了 Instance Editable 的变量) string 转成 message 传给 SetText」「怎么动态创建 localizes message」是两个常年高热的提问帖。答案都指向同一个模式——往下看。

二、message 存在的意义:文本资源,不是字符序列

为什么 Epic 要造两个「文字类型」为难大家?因为它们回答的是两个不同的问题。

string字符序列:一串确定的 []char,长度可数、可遍历、可拼接,程序怎么写它就怎么显示。而 message可本地化的文本资源:它代表「一句要给玩家看的话」,具体显示成中文、英文还是日文,由玩家的语言设置决定。你的岛屿发布到全世界时,message 类型的文本可以进入本地化管线被翻译,string 则永远是硬编码的那串字符。

所以 UI API 收 message 不是刁难,而是在类型系统层面提醒你:给玩家看的文字,应该是可翻译的。这和 Verse 一贯的哲学一致——把隐患(这里是「硬编码文案没法本地化」)显式化成类型区别。

所以那些 UI 设备节点非要 message 不是刁难,而是在提醒你:给玩家看的文字,应该是能翻译的。这和 Verse 一贯的哲学一致——把隐患(这里是「文字写死了、没法翻译成别国语言」)提前摆到明面上,让你在连线时就得正视它。

三、标准解法:<localizes> 函数 + 插值

从 string 到 message 的官方正道,是定义一个带 <localizes> 说明符的函数,函数体就是一个带插值的字符串字面量:

从 string 到 message 的官方正道,是做一个蓝图函数,给它挂上 <localizes> 这个标记,函数里只放一句带插值的文字:

message_helper.verse
# 万能包装器:把任意动态 string 包成 message
MakeMessage<localizes>(Text:string):message = "{Text}"

# 带参数版:模板固定、数值动态,更贴近本地化本意
ScoreMessage<localizes>(Score:int):message = "得分:{Score}"

用法示意:

billboard_text_device.verse
using { /Fortnite.com/Devices }

billboard_text_device := class(creative_device):

    @editable
    WelcomeText:string = "欢迎来到竞技场"

    OnBegin<override>()<suspends>:void =
        Welcome := MakeMessage(WelcomeText)
        # Welcome 现在是 message,可传给需要 message 的 UI API
        # TODO: 以 Verse API Reference 为准

读图翻译:这个告示牌设备把 WelcomeText 做成勾了 Instance Editable(@editable)的变量,放进关卡后能在 Details 面板直接改文字;开局(Event BeginPlay)把它喂给刚才的 MakeMessage,拿到一个 message,就能连进那些只认 message 的 UI 节点了。

拆解一下这套魔法:<localizes> 告诉编译器「这个函数产出的是本地化文本资源」,于是函数体里的字符串字面量被编译成 message 而不是 string;插值 {Text}{Score} 则负责把运行时的动态值嵌进这段文本。第 6 课学的插值语法,在这里原封不动地复用——只是产物从 string 变成了 message。

拆解一下这套魔法:<localizes> 等于给这个函数贴张标签「我产出的是可翻译的文本资源」,于是函数里那句文字被当成 message 而不是普通 string;插值 {Text}{Score} 则负责把运行时拿到的动态值嵌进这段文本。第 6 课学的插值语法,在这里原封不动地复用——只是产物从 string 变成了 message。

一条实践建议:能用「带参数版」就别用「万能包装器」。ScoreMessage(120) 这种「模板固定、数值动态」的形态,翻译人员能拿到完整句式去翻译;而 MakeMessage(任意字符串) 只是把 string 洗成 message,本地化管线拿它没办法。前者才是 message 设计的本意,后者是应付 @editable 动态文案的权宜之计——两种都会用,才算真正翻过了这堵墙。

四、随堂小测 ★

想把一个 @editable 的 string 显示到需要 message 的 UI 上,正确做法是?想把一个 @editable(勾了 Instance Editable 的)string 显示到需要 message 的 UI 上,正确做法是?

message 和 string 的本质区别是什么?

来源与延伸

本文整理自 Epic 开发者论坛的两个高热讨论:

论坛讨论:How to convert strings from exposed properties into messages (SetText) ↗
论坛讨论:How do you dynamically create <localizes> message ↗