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

给 enum 手写 ToString:case 映射模式

第 7 课说过:enum 不能直接 Print,也不能塞进字符串插值enum 没法用 Print String 直接打到屏幕上,也没法拼进你要显示的那行文字里。这一页给出社区沉淀下来的标准解法——一个 case 映射函数;把它命名为 ToString,还能白拿一个隐藏福利。

一、问题:enum 是个哑巴

调试的时候你多半干过这件事:状态机走到哪一步了?打个日志看看。于是顺手写下 Print("当前状态:{Current}")——编译器立刻泼来冷水:enum 没有内置的文字形态,既不能直接 Print,也不能出现在字符串插值里。它同样不能转成 int(成员背后压根没有整数),论坛上「Casting enum to string and int」这条高热度帖子确认了这是语言的刻意设计,不是遗漏。

调试的时候你多半干过这件事:状态机走到哪一步了?打条日志(Print String)看看。于是顺手写下 Print("当前状态:{Current}")——Compile 立刻泼来冷水:enum 没有自带的文字形态,既不能用 Print String 直接打出来,也不能拼进那行要显示的字里。它同样不能变成整数(条目背后压根没有数字),论坛上「Casting enum to string and int」这条高热度帖子确认了这是语言的刻意设计,不是遗漏。

为什么刻意?因为 enum 成员的身份就是「它自己」:zombie_state.Walk 不是 1,也不是 "Walk",它只是三个铭牌里的一块。要给它配上人类可读的文字,Verse 的态度是——你自己说了算,语言不替你猜。

二、标准解法:case 映射函数

解法朴素得让人安心:写一个函数,用 case 把每个成员映射到一段字符串。这正是第 7 课末尾那个 PhaseLabel 的正式版:

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

zombie_state := enum{Idle, Walk, Attack}

# 命名为 ToString:字符串插值会自动找到它(见下文)
ToString(State:zombie_state)<computes>:string =
    case (State):
        zombie_state.Idle => "Idle"
        zombie_state.Walk => "Walk"
        zombie_state.Attack => "Attack"

zombie_debug_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        Current := zombie_state.Walk
        Print("僵尸状态:{Current}")

▸ 读图翻译:先建一个 zombie_state 枚举资产(Idle / Walk / Attack)。ToString 是一个蓝图函数,输入一个枚举条目,用 case 分流:Idle 吐 "Idle"、Walk 吐 "Walk"、Attack 吐 "Attack",各给一段文字。最后设备开局(Event BeginPlay)里,把当前状态用 Print String 打出来——注意插值 {Current} 竟然直接认识这个枚举了,秘密下一节揭晓。

几个细节值得停一停。其一,case 写全了三个成员,没有兜底分支——对默认 closed 的 enum 这是合法的,而且是更好的写法:将来你添了 zombie_state.Run 却忘改这个函数,编译器会当场点名,安全网白送。其二,函数上的 <computes> 是一个效果说明符,大意是「纯计算,不碰外部世界」,论坛原帖就是这么写的;效果说明符的完整故事后面章节再展开,现在照抄即可。

几个细节值得停一停。其一,case 把三个条目写全了,没留兜底出口(蓝图 Switch on Enum 里那个 Default 引脚)——对默认 closed 的枚举这是允许的,而且更好:将来你加了 zombie_state.Run 却忘了改这个函数,Compile 会当场点名漏了谁,安全网白送。其二,函数名后面那个 <computes> 是一个「说明符」,意思是「这函数只做纯计算,不去碰外部世界」,论坛原帖就是这么写的;说明符的完整故事后面章节再展开,现在照抄即可。

三、隐藏福利:插值自动认领 ToString

注意上面示例的最后一行:Print("僵尸状态:{Current}")——插值里直接放了 enum,居然编译过了?这就是把函数命名为 ToString 的隐藏福利:字符串插值 {X} 的本质是调用作用域内可见的、匹配 X 类型的 ToString 重载。int 和 float 能插值,靠的是标准库自带的 ToString;你为 zombie_state 补上一个同名重载,enum 从此也能直接插值打印,日志代码干净得像它天生就会说话。

注意上面示例的最后一行:Print("僵尸状态:{Current}")——花括号里直接塞了个枚举,居然过了 Compile?这就是把函数命名为 ToString 的隐藏福利:一段文字里的 {X},背后其实是去找一个能把 X 变成文字、并且当前能看得到的 ToString 函数来顶班。数字(int、float)之所以能直接写进 {},是因为引擎自带了给它们用的 ToString;你给 zombie_state 也补上一个同名的,枚举从此也能直接拼进文字里打印,日志代码干净得像它天生就会说话。

顺带一提两个周边事实。原帖发帖人还许愿过 enum{Missing = "missing"} 这类「定义时顺便挂字符串」的语法——目前并不存在,可以当作「这门语言仍在演进」的注脚。另外,官方社区教程《Understanding, Accessing, and Making an Enum》覆盖了 enum 的更多实战用法,比如把 enum 变量标记为 @editable 后,它在编辑器面板上会变成下拉框,策划同学可以不碰代码就切换状态——enum 的类型安全就这样一路延伸到了编辑器 UI。

顺带一提两个周边事实。原帖发帖人还许愿过 enum{Missing = "missing"} 这种「建枚举时顺手给条目挂一段文字」的写法——目前还没有,可以当作「这门语言仍在演进」的注脚。另外,官方社区教程《Understanding, Accessing, and Making an Enum》覆盖了 enum 的更多实战用法,比如给枚举变量打开 Instance Editable(@editable,变量上那只小眼睛),它在关卡的 Details 面板里就会变成一个下拉框,策划同学不碰代码就能切换状态——enum 的类型安全就这样一路延伸到了编辑器 UI。

把 enum 的映射函数命名为 ToString(而不是随便叫 GetLabel),有什么额外好处?

来源

本文整理自 Epic 开发者论坛与官方社区教程:Casting enum to string and int — Epic 开发者论坛 ↗ · Understanding, Accessing, and Making an Enum in Verse Code — Epic 官方社区教程 ↗