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

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

官方文档淡淡的一句「string 是 []char 的别名,char 是 UTF-8 code unit」,背后藏着一整套文本编码知识。为什么 'e' 是 char 而 'é' 是 char32?为什么中文字符串的 Length 和肉眼字数对不上?这一页带你从「会用字符串」升级到「理解字符串」。

一、两种字符:char 与 char32

Verse 有两个字符类型,这在编程语言里不算常见:

char——一个 UTF-8 编码单元(code unit),可以理解成「编码后的一个字节格子」。
char32——一个完整的 Unicode 码点(code point),即「人类眼中的一个字符」在 Unicode 字符表里的编号。

字符字面量(就是直接用引号写出来的字符)都用单引号,但类型由内容决定:'e' 是 ASCII 字符,UTF-8 里恰好占一个编码单元,所以它是 char;而 'é' 这种带附加符号的字符,UTF-8 编码要占多个编码单元,单个 char 装不下,于是它的字面量类型是 char32——用完整码点才存得下。

char_demo.verse
# ASCII 字符:一个 UTF-8 编码单元,类型是 char
Letter:char = 'e'

# 'é' 需要多个 UTF-8 编码单元,字面量是 char32
# Accent:char32 = 'é'

char 和 char32 的能力被刻意限制在最小集合:比较(可失败的 =<>)和持有值,没有加减、没有大小写转换。想对字符做更多事,一般先借插值 "{Ch}" 把它升格成 string 再处理——第 6 课拓展页的 Split 实现用的正是这一招。

char 和 char32 的本事被刻意压到最小:只能比较(用「可能不成立」的 =<>,所以要放进 Branch/if 里)和存一个值,不能加减、不能转大小写。想对字符多做点事,一般先用插值 "{Ch}" 把它升格成 string 再处理——第 6 课拓展页的 Split 实现用的正是这一招。

二、UTF-8:变长的「字符打包术」

Unicode 给世界上每个字符发了一个编号(码点):拉丁字母、汉字、假名、emoji,人人有号。但「编号」怎么存进内存?这就是编码方案的活儿,而 UTF-8 是当今的绝对主流。它的核心思想是变长打包:

字符类型 例子 UTF-8 编码单元数(典型)
ASCII 字母 / 数字 / 半角标点 e、7、! 1
带附加符号的拉丁字母 é、ü 2
中日韩文字 你、剑 3
emoji 等高位码点 各类表情符号 4

越常用的字符编码越短——这让纯英文文本极省空间,同时又能表示全部 Unicode 字符。代价是:「字符数」和「编码单元数」从此不再是一回事。

这直接解释了第 6 课埋的伏笔:string 是 []char,即 UTF-8 编码单元的数组,所以 .Length 数的是编码单元,不是肉眼字符。按 UTF-8 规则推算,"你好" 的 Length 很可能是 6 而不是 2——给玩家名、聊天文本做长度校验的中文创作者,千万别拿 Length 当「字数」用。动手验证只要两行:

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

utf8_lab_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        English := "Verse"
        Chinese := "你好"
        Print("English 长度:{English.Length}")
        # 亲手跑一遍:下面这个数字很可能不是 2
        # 具体计数以编辑器实测为准
        Print("Chinese 长度:{Chinese.Length}")

读图翻译:开局(Event BeginPlay)记下两个字符串,再用两个 Print String 分别打出它们的 .Length——英文那行会是 5,中文那行很可能不是 2,亲眼看看编码单元和字数的差别。

同理,失败上下文里的下标取值 Chinese[0] 拿到的是第一个编码单元,而不是「你」这个完整汉字——对多字节文本做逐字符处理时,这是必须心里有数的边界。

同理,在 Branch(if)里按下标取 Chinese[0],拿到的是第一个编码单元,而不是「你」这个完整汉字——对多字节文本做逐字符处理时,这是必须心里有数的边界。

三、字符串比较:逐码点、分大小写

string 的相等比较 = 和不等比较 <> 都是可失败表达式(所以住在 if 里)string 的相等比较 = 和不等比较 <> 都是「可能不成立」的判断,所以得放进 Branch(if)里,规则简单粗暴:按码点逐个比对,大小写敏感"Apple" = "apple" 会失败,因为 A 和 a 是两个不同的码点;不存在「忽略大小写比较」的内置开关,而且标准库也没有 ToUpper / ToLower 帮你先统一大小写——需要时得自己写映射(或者在设计上规避,比如玩家输入统一转成选项而非自由文本)。

把这一页和第 6 课正文连起来,你就集齐了 Verse 字符串的完整心智模型:字面量是台词,插值是填空,string 是 UTF-8 编码单元的数组,char / char32 是它的最小零件。会用,也知道底下是什么——处理中文创作场景时,这层理解就是你比别人少踩坑的底气。

四、随堂小测 ★

字面量 'e' 和 'é' 的类型分别是什么?

用 .Length 校验中文玩家名的「字数」,为什么不靠谱?

来源与延伸

本文整理自 Epic 官方文档,并参考了社区手册 Book of Verse 的字符类型章节(社区资料,细节以官方文档与编辑器实测为准):

官方文档:string in Verse ↗
社区手册:Book of Verse — Primitives ↗