技巧 · EXTRA
Verse 编译报错怎么读:文件、行列、错误码
Verse 的报错是三段式的。学会拆它,再认全新手最常撞的五条,报错就从劝退变成导航——顺带和蓝图的 Compiler Results 面板做个对照。
进入拓展 →上一课你把节点图读成了缩进块,这一课动手造第一个东西。在蓝图里,你右键新建一个 Blueprint Class、挑一个父类,就得到一个资产,双击打开有事件图、有变量面板、有 Details 面板。Verse 里对应的动作只有一句话:新建一个 .verse 文件,在里面声明一个类。这两件事严格并列,我们一格一格对着拆。
先把你熟悉的那套流程完整回忆一遍。你要做一扇会自己开合的门,于是:在 Content Browser 里右键 → Blueprint Class → 弹窗里挑一个父类(比如 Actor)→ 起名 BP_MyDoor。Content Browser 里多出一个资产,双击打开,你看到三块地方:中间的 Event Graph(事件图),左边 My Blueprint 面板里的 Variables 一栏(变量面板),以及选中节点或变量时右边亮起的 Details 面板。
Verse 里做同一件事,动作是这样的:新建一个叫 my_door.verse 的纯文本文件,在里面写一行 my_door := class(creative_device):,然后往下缩进,把变量和事件都写进去。
把两边并排看,是严格的一一对应:
名字 := class(父类):;OnBegin 的函数,以及它下面再缩进一级的几行。只有一处不是一一对应,而且这一处值得单独记住:蓝图里「资产」和「类」是同一件东西——一个 .uasset 文件就是一个类,想要两个类就得建两个资产。Verse 里文件只是个容器:.verse 文件里可以只有一个类,也可以有五个类、三个函数和一个枚举。文件名和类名甚至不必相同(官方风格是让它们相同,方便找)。「一个文件一个类」在 Verse 里是习惯,不是规矩。
还有一处差别现在就该心里有数:蓝图资产是二进制的,只有 Unreal 编辑器打得开;.verse 是纯文本,记事本都能打开,Git 能逐行 diff。这件事对协作的影响相当大,我们放在本课的第二个拓展页里讲。
下面这九行,是一个完整、能编译、能跑的 Verse 类。你在蓝图里做的每一步,它都做了一遍:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
my_door := class(creative_device):
DoorName:string = "北门"
var OpenCount:int = 0
OnBegin<override>()<suspends>:void =
Print("{DoorName} 已就位")
using { /Fortnite.com/Devices } —— 这个类能用到哪些资产库using 那几行的作用,是宣布「下面这段代码要用到哪几个库里的东西」。/Fortnite.com/Devices 里住着 creative_device,/UnrealEngine.com/Temporary/Diagnostics 里住着 Print。没写 using,下面用到的名字编译器就不认得。
蓝图里没有一个长得一模一样的操作,最接近的是「这个蓝图能用到哪些资产库」这件事:你在项目设置里启用一个插件、或者在 Content Browser 里能看见某个模块的资产,那些节点才会出现在右键搜索框里。区别在于蓝图默认把整个引擎的可用节点都摊在你面前,而 Verse 要求你在文件顶部逐个点名。这是显式换来的可读性:看一眼文件头,就知道这个类依赖谁。using 的完整规则(模块、路径、命名冲突)是下一课的主题。
my_door := class(creative_device): —— 新建 Blueprint Class 并选父类这一行等价于你在蓝图里点完「Blueprint Class → 选 Actor → 起名 BP_MyDoor」之后拿到的那个资产。拆成三块:
my_door —— 类名,也就是你在起名框里敲的那串字。Verse 的类名用 lower_snake_case(全小写 + 下划线),这是官方 Verse Code Style Guide 定的规矩;蓝图那边的习惯是 BP_MyDoor 这种 PascalCase 加前缀,两边的命名规范对不上,别硬套;:= —— 读作「定义为」。左边是名字,右边是这个名字到底是什么。注意它和单独的 = 不是一回事:Verse 里 = 是比较,定义新东西认准 :=;class(creative_device) —— 「我是一个类,父类是 creative_device」。括号里这个名字,就是你在新建蓝图弹窗里点选的那个父类。想换父类?蓝图要走 Class Settings → Parent Class 下拉菜单,Verse 就是改括号里这一个单词。行尾的冒号加下一行缩进 4 空格,是上一课讲过的老规矩:缩进就是归属。往里缩的那些行,全都算这个类的内容。
缩进一级的 DoorName 和 OpenCount 叫作类的字段(field),它们就是 My Blueprint 面板里 Variables 那一栏。DoorName:string = "北门" 逐字翻译过来是:新建一个变量,名字 DoorName,Variable Type 选 String,Default Value 填「北门」。
差别在 var 这个词上。蓝图里新建的变量天生就能接 Set 节点;Verse 反过来,默认不可变,想让它在运行时被改,声明前面必须加 var。所以 DoorName 是「刻死的名字牌」,var OpenCount 是「运行时会被改的格子」。常量、变量与 set 的完整故事在第 8 课,这里先认脸。
OnBegin<override>()<suspends>:void = —— Event BeginPlay这一行是新人第一眼的劝退现场:六个零件挤在一行里。但它整行加起来只表达一件事——「我要重写父类的 Event BeginPlay」。一个零件一个零件过:
| 零件 | 读作 | 蓝图里对应什么 |
|---|---|---|
OnBegin |
「开场时」 | 就是 Event BeginPlay 那个红色事件节点。游戏体验一开始,引擎自动触发它,不用你去调用。函数名用 PascalCase(首字母大写),和类名那种全小写加下划线正好相反 |
<override> |
「重写徽章」 | 对应蓝图里「重写父类事件」这件事:在 My Blueprint 面板的 Override 下拉菜单里挑一个父类函数,编辑器就给你生成一个能改写的版本。蓝图靠 UI 保证你挑的确实是父类真有的函数;Verse 没有 UI,就靠这块徽章向编译器申报。漏写它,编译直接报错 |
() |
「不收参数」 | 参数列表,对应事件节点上的输入引脚。OnBegin 一个参数都不收,但空括号一个都不能省 |
<suspends> |
「能等待徽章」 | 声明这个函数可以中途等待——里面能放 Delay 那样的延迟节点(Verse 里叫 Sleep)。父类的 OnBegin 带着它,你重写时也必须带,少一块就跟父类对不上号 |
:void |
「不交作业」 | 返回类型。void 表示不往外交任何值——对应蓝图里 Return 节点没有输出引脚,只管干活 |
= |
「定义为」 | 声明部分到此结束;真正要跑的那串节点,从下一行再缩进一级开始接 |
现阶段的记法很粗暴:这一整行原样背下来。它的官方语义就是「重写此函数,在游戏体验开始时加入自定义逻辑」。顺带一提,OnBegin 有个孪生兄弟 OnEnd<override>():void =,在体验结束时运行,而且不带 <suspends>。
把四块拼起来:using 是这个类的资产库清单,class(父类) 是新建资产并选父类,字段是变量面板,OnBegin 那一块是事件图。你在蓝图编辑器里点了半天鼠标做的事,在这里是九行文本。
类写好了,怎么让它真的动起来?这里必须把两件事分清楚:类的写法是 Verse 语言的规矩,怎么挂载、怎么编译、怎么放进世界是运行环境的规矩。前者稳定,后者会变。
今天,唯一能真正跑 Verse 的地方是 UEFN(Unreal Editor for Fortnite)——Verse 自 2023 年 3 月起就在 UEFN 的生产环境里运行至今。所以下面这四步是「今天的运行环境」的操作手册,不是这一课的知识点:
.verse 文件,里面就是上一节那个骨架;.verse 文件,不像蓝图那样单个资产单独 Compile;my_door 会出现在 Content Browser 里。像拖蓝图资产一样把它拖进关卡——只有被放进关卡的实例才会执行,拖两个就有两份逻辑各跑各的;Print 的内容会出现在 Output Log 面板和游戏画面上。为什么必须写 class(creative_device) 才能拖进关卡?因为在今天的 UEFN 里,creative_device 是「能被放进关卡的东西」的父类——这条要求来自 Fortnite 这个运行环境,不是 Verse 语言本身。Verse 里完全可以写一个不继承任何东西的普通类,它只是没法被拖进 Fortnite 的关卡而已。
明天会变的正是这一层。Epic 在 UE6 里把 UE5 与 UEFN 合并成一个引擎,并带来了 Scene Graph——一套从零基于 Verse 构建的玩法框架,用 entity + component 取代 Actor 继承树。在那套框架下,你写的类会以 component 的形式挂在 entity 上,而不是继承 creative_device 再拖进关卡(第 26–27 课细讲)。UE6 Early Access 的目标是 2027 年底;Actor 与 Blueprint 在 UE6 EA 及早期版本中完整支持,弃用要等 Scene Graph 足够成熟,时间未定。
但请注意变的是什么:变的是挂载方式——父类叫什么、怎么进世界。名字 := class(父类):、字段就是变量、<override> 就是重写父类事件——这些类的写法本身一个字都不变。今天你在 UEFN 里练熟的,就是明天在 UE6 里直接用的。
把这一课的零件拼齐,看它一行行醒过来。下面这台 my_door 比刚才多了两行:开场三秒后把 OpenCount 加一,再报一次数。点「运行下一步」,每一步的说明都会告诉你:这一步在蓝图里是哪个动作。
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
my_door := class(creative_device):
DoorName:string = "北门"
var OpenCount:int = 0
OnBegin<override>()<suspends>:void =
Print("{DoorName} 已就位")
Sleep(3.0)
set OpenCount += 1
Print("{DoorName} 开门次数:{OpenCount}")
点「运行下一步」,看代码怎么一行行执行。
留意第 7、8 两行的对比:同样是「在变量面板里加一个变量」,Verse 逼你多做一个决定——这个值到底会不会变?会变才写 var。别人打开你的类,一眼就知道哪些是死的、哪些是活的。
把「造一个类」这件事拆成六个动作,两边逐个对齐。差异那一列才是重点。
| 蓝图里的做法 | Verse 里的写法 | 差异 |
|---|---|---|
新建资产:Content Browser 右键 → Blueprint Class,得到一个 .uasset |
新建一个 .verse 文本文件,在里面写一行类声明 |
蓝图里「一个资产 = 一个类」;Verse 里文件只是容器,一个文件可以装多个类。文件名与类名可以不同 |
| 选父类:新建弹窗里挑 Actor / Pawn / 自定义父类 | class(creative_device) 括号里那个名字 |
蓝图改父类要走 Class Settings → Parent Class 下拉菜单;Verse 改父类就是改那一个单词 |
| 变量面板:My Blueprint → Variables → 加变量、选 Variable Type、填 Default Value | 类下面缩进一级的字段:DoorName:string = "北门" |
蓝图变量天生可写;Verse 默认不可变,要能被 Set 必须写 var |
| 事件图:Override 下拉菜单挑出 BeginPlay,从事件节点拉执行线 | OnBegin<override>()<suspends>:void = 下面缩进的几行 |
蓝图靠连线表达顺序、靠 UI 保证重写合法;Verse 靠缩进表达顺序、靠 <override> 徽章向编译器申报 |
| 编译按钮:工具栏 Compile,编译当前这一个蓝图资产 | Build Verse Code(Ctrl+Shift+B) | 蓝图逐个资产编译,Verse 是整个项目一起编译:任何一个文件写错,全项目都过不了 |
| 放进关卡:把蓝图资产拖进 Viewport,得到一个实例 | 编译成功后类出现在 Content Browser,同样拖进关卡 | 今天在 UEFN 里,只有认 creative_device 当父类的 Verse 类才拖得进关卡;这是 Fortnite 这个运行环境的要求,不是 Verse 语言的要求 |
这些差异几乎全部来自同一个根:蓝图把类存成二进制资产、靠编辑器 UI 维护正确性;Verse 把类存成纯文本、靠编译器维护正确性。你在蓝图里挑父类时不可能挑到一个不存在的类,因为下拉菜单只列存在的;Verse 里你完全可以敲错一个父类名,所以编译器必须在编译期把它拦下来。同理,<override> 这块徽章在蓝图里根本不需要——Override 菜单本身就是证明。文本失去了 UI 的保护,就用显式声明补回来。
另一条根是编译粒度。蓝图资产各编译各的,一个坏掉的蓝图不影响别的;Verse 是整项目一次编译,好处是跨文件的错误当场暴露,代价是任何一个文件里的手误都会让全项目停摆。这也是为什么「读懂编译报错」在 Verse 里是必修课,而不是选修——见本课第一个拓展页。
一道填空 + 三道选择。答对拿星星,答错随便重试,没有惩罚。
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
# 新建一个类,父类选 creative_device
my_gate := ____(____):
# 重写父类的 Event BeginPlay
OnBegin<____>()<suspends>:void =
Print("我的第一个 Verse 类上线了!")
在蓝图里,「右键 → Blueprint Class → 弹窗里选父类 Actor → 起名」这一整套动作,在 Verse 里对应什么?
OnBegin 那行的 <override> 徽章,对应蓝图里的哪个操作?
关于「今天在 UEFN 里跑 Verse」与 UE6 的关系,哪个说法准确?
技巧 · EXTRA
Verse 的报错是三段式的。学会拆它,再认全新手最常撞的五条,报错就从劝退变成导航——顺带和蓝图的 Compiler Results 面板做个对照。
进入拓展 →拓展 · EXTRA
项目里的 Verse 代码怎么组织,文件与模块是什么关系,以及纯文本的 .verse 和二进制的 .uasset 在版本控制与协作上到底差在哪。
进入拓展 →拔高 · EXTRA
类是图纸,实例才是场上的东西。这一页讲清两边的实例化各是什么动作,以及为什么 Verse 类的字段可以先不给默认值。
进入拓展 →