Verse Wiki — 写给 UE6 时代的 Verse 互动手册
第二章 · 第 6 课

string 与字符串插值:让代码开口说话

数字会算账,还得有人会播报。这一课把 string 拆开讲透:双引号里的台词、花括号 {X} 的实时填空魔法、加号拼接,以及用 Print 打调试日志的艺术——顺便拆掉「大括号转义」这个新手必踩的地雷。

一、string 字面量:双引号里的台词

游戏里到处是文字:玩家名、任务描述、击杀播报、商店里那句「装备已售罄」。在 Verse 中,这些文字都装在 string 类型里。写一段 string 最直接的方式是「字面量」(蓝图里没有这个词,就是你在文本框里直接敲进去的那句话,不接任何计算节点)——把台词原样放进一对双引号:

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

npc_device := class(creative_device):

    # 常量 string:开场白定了就不改
    Greeting:string = "欢迎来到新手竞技场"

    # var string:称号会随战绩变化
    var Title:string = "见习勇者"

读图翻译:这段等于新建一个蓝图类(基于 creative_device),在变量面板里加两个 string 变量——Greeting 是常量(不给它接 Set 节点,值就焊死),Title 允许中途改(后面能接 Set 节点)。

双引号里几乎什么都能放:中英文、数字、标点、空格,统统合法。上一课的规则在这里照常生效——不写 var 就是挂牌即焊死的常量,写了 var 以后改动时要喊 set 口令。上一课的规则在这里照常生效——从没给它接过 Set 节点、值焊死不改的就是常量;想让它中途变值的,改的时候接一个 Set 节点把新值送进去(这道「口令」在 Verse 里写作 set)。

顺带认识一位小个子亲戚:char,单个字符类型,字面量用单引号,比如 'e'。事实上 string 在 Verse 里的真身就是 []char——一串字符排成的数组,这个身世会在后面数组课和本课的拓展页里派上大用场。现在只要记住:双引号是一句话,单引号是一个字。

二、插值 {X}:字符串里的实时填空

固定台词是死的,好日志得能报出变量的实时数值。Verse 的办法优雅极了:在字符串里写一对花括号,把变量名放进去——Print("Score: {Score}")。打印的一瞬间,{Score} 会被换成格子里的真实内容。这招叫「字符串插值」:台词挖个空,Verse 帮你实时填好。

更妙的是,填进去的不必是 string:int、float 也能直接插。Verse 会在幕后调用该类型的 ToString() 函数,把数字翻译成文字再嵌进句子。花括号里甚至可以写表达式,比如 {Coins * 2}——只有一条红线:可失败的表达式不能直接插(比如数组下标取值),得先在 if 里把值安全地取出来,再插那个结果。这条红线我们在「常见坑」里细说。

更妙的是,填进去的不一定是文字:整数(int)、小数(float)也能直接塞进花括号——Verse 会在幕后自动把数字转成文字再嵌进句子,不用你手动转换。花括号里甚至能放一小段计算,比如 {Coins * 2}。只有一条红线:「有可能失败的取值」不能直接插(比如按下标从数组里取一个元素,万一越界就取不到),得先用一个 Branch(Verse 里写成 if)确认能取到、把值稳稳拿出来,再插那个结果——只有这条线走得通才继续。这条红线我们在「常见坑」里细说。

下面这台「播报员」把常量、变量、插值、转义一次串起来。点「运行下一步」,亲眼看每行代码干了什么。

announcer_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

announcer_device := class(creative_device):

    HeroName:string = "夜行者"
    var Coins:int = 0

    OnBegin<override>()<suspends>:void =
        Print("欢迎回来,{HeroName}!")
        set Coins += 30
        Print("钱包:{Coins} 金币")
        Print("按 \{E\} 打开背包")
输出日志

点「运行下一步」,看代码怎么一行行执行。

注意最后一行:想打印真正的花括号,必须写 \{\}。因为花括号在字符串里是保留给插值的——这正是本课头号大坑,第四节专门审判它。

三、拼接与转义:文字的加法和暗号

两段文字合成一段,用的就是最朴素的加号。string 的 + 是「接在后面」:"Hello " + Name 得到一句完整的问候;配合 set配合 Set 节点 还能不断往后追加,这在逐步拼一句长播报时特别顺手:

concat_demo.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

concat_device := class(creative_device):

    var Report:string = "战报:"

    OnBegin<override>()<suspends>:void =
        Kills := 3
        set Report += "击杀 {Kills} 人,"
        set Report += "存活到最后"
        Print(Report)
        Print("战报共 {Report.Length} 个字符单元")

读图翻译:游戏一开始(Event BeginPlay),先把 Report 起头设成「战报:」、把 Kills 记成 3;接着两次用 Set 节点往后接文字(+= 就是拿旧值接上新段再塞回去);最后用两个 Print String,分别打出整句战报、以及这句话的长度。

最后一行偷偷剧透了 string 的数组身世:既然它是 []char,那数组的 .Length 它也有,返回一个 int(注意:对中文这类多字节字符,计数按编码单元算,可能比肉眼字数多——拓展页有详解)。

再来认识字符串里的「暗号系统」——反斜杠转义。有些字符没法直接敲进字面量,得用反斜杠开头的暗号来表示:

写法 得到 什么时候用
\{\} { 和 } 想打印真花括号,而不是插值
\" " 台词里带引号:他说 \"冲!\"
\\ \ 想打印反斜杠本身
\n 换行 一条日志分成两行
\t 制表符 日志列对齐

规律很好记:凡是「在字符串里有特殊身份」的字符——花括号管插值、双引号管边界、反斜杠管暗号——想按原样打印,就在前面加一个 \,相当于跟编译器说「这个字免检,照原样放行」。

四、Print 调试输出的艺术(与常见坑)

Print(蓝图里的 Print String 节点) 是你在 UE6 里最忠实的侦察兵:代码行为不对劲时,在关键位置插几句 Print,日志会把「程序到底走到了哪、变量到底是几」原原本本报给你。几条老兵经验:

带上前缀。Print("[商店] 余额:{Coins}") 比光秃秃的数字好查得多——日志一多,没名字的输出等于没输出。
打「前后对比」。改动一个变量前后各打一句,状态变化一目了然,比盯着代码猜快十倍。
用完就撤。调试 Print 是脚手架,功能验收后删干净,别让正式版日志刷屏。

接下来是本课的「坑位巡逻」,四个坑个个都是新手高频事故:

坑一:Print(42) 编译不过。Print 只收 string,int、float 一概拒收。想打印数字,请让插值当翻译:Print("{42}")Print("血量:{Health}")。忘了这条,编译器会用类型不匹配的错误提醒你。

坑一:直接 Print 一个数字会亮红。Print String 的输入引脚只认文字(string),你把整数、小数直接连进去,引脚类型对不上,点 Compile 就报错。想打印数字,让插值当翻译:Print("{42}")Print("血量:{Health}")——把数字包进花括号,Verse 自动转成文字。忘了这条,Compile 会用「类型不匹配」的红字提醒你。

坑二:裸写花括号,编译器当场翻脸。Print("按 {E} 互动") 看着无辜,但编译器认定 {E} 是插值,转头去找变量 E——找不到就报错;就算真有个 E,打印的也是它的值而不是「{E}」三个字符。想要真花括号,老老实实 \{E\}

坑二:裸写花括号,一点 Compile 就翻脸。Print("按 {E} 互动") 看着无辜,但 Verse 认定 {E} 是要填空的插值,转头去找一个叫 E 的变量——找不到就报红;就算真有个 E,打出来的也是它的值,而不是「{E}」这三个字符。想要真花括号,老老实实写 \{E\}

坑三:可失败表达式不能直接插。"{MyString[0]}" 这种写法编译不过——数组下标可能失败,而插值不给失败留后路。正确姿势是先掷骰再报数:if (First := MyString[0]) { Print("首字符:{First}") }

坑三:「可能失败的取值」不能直接插。"{MyString[0]}" 这样写会亮红——按下标从数组里取字符有可能扑空(越界),而插值不接受「万一失败」的情况。正确姿势是先用一个 Branch(Verse 写成 if)确认取得到,再报数:if (First := MyString[0]) { Print("首字符:{First}") }——只有这条线走得通,才会往下打印。

坑四:float 插出来自带六位小数。"{1.5}" 打印的是 1.500000——float 的 ToString 固定六位小数,做 UI 文本时尤其扎眼,需要手动格式化(上一课的拓展页里有四舍五入配方)。

坑四:小数(float)插出来自带六位小数。"{1.5}" 打印的是 1.500000——Verse 把小数转成文字时固定补足六位小数,做 UI 文本时尤其扎眼,需要手动格式化(上一课的拓展页里有四舍五入配方)。

最后一条预防针:Verse 标准库的字符串工具箱非常克制,没有 Split、Replace、Contains、ToUpper 这些别的语言里的老朋友。缺的轮子社区早就造好了——见本课拓展页。

最后一条预防针:Verse 的字符串工具箱非常克制,没有 Split、Replace、Contains、ToUpper——这些你在蓝图 String 节点库里随手就有的节点,Verse 标准库统统没内置。缺的轮子社区早就造好了——见本课拓展页。

五、动手时间:补全播报台词

光看不练假把式。下面这台「升级播报机」被挖掉了三个关键点:一处插值、一处拼接符号、一处花括号转义。把它们填回去,再点「检查答案」。

读图翻译:开局(Event BeginPlay)先用 Set 节点把 Level 加 4,剩下三个空就是本课三件套——插值、拼接、转义——各补一处。

levelup_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

levelup_device := class(creative_device):

    var Level:int = 1

    OnBegin<override>()<suspends>:void =
        set Level += 4
        # 空 1:用插值报出 Level(花括号 + 变量名)
        Print("当前等级:____")
        # 空 2:把两段文字接成「夜行者大人」
        Title := "夜行者" ____ "大人"
        Print("获得称号:{Title}")
        # 空 3:转义左花括号,打印出「{VIP}」
        Print("____VIP\}")

六、关卡挑战 ★

台词功底验收时间。答错零惩罚,可以一直重试。

想把变量 Kills(int 类型)打进日志,写了 Print(Kills),会发生什么?

想在日志里打印出真正的一对花括号 {},正确的写法是?

Print("{1.5}") 会打印出什么?

拓展阅读

技巧 · EXTRA

自己动手实现 Split

标准库没有 Split?社区早就写好了轮子——一个函数把本课全部技能串起来练。

进入拓展 →

拓展 · EXTRA

message 与 <localizes>

UI 文本为什么不是 string?一堵每个创作者都会撞的墙,和翻墙的标准姿势。

进入拓展 →

拔高 · EXTRA

字符串的底层:char、char32 与 UTF-8

为什么 'e' 是 char 而 'é' 是 char32?一次看懂文本编码,中文创作场景尤其实用。

进入拓展 →