Verse Wiki — 写给蓝图作者的 Verse 手册
第二章 · 第 7 课

注释、模块与 using:给代码留言,给工具分箱

第二章前三课对齐了名词、结构和类,这一课收尾:代码本身怎么分门别类地放。注释是写给人看的留言条,模块是分了格的工具墙,using 是把工具箱搬上工作台——蓝图靠资产与文件夹维持的那套秩序,在文本世界里就是这三样。

一、注释:写给队友(和三个月后的你)的留言条

第 4 课给了你一张名词对照表,第 5 课把节点图掰成了缩进块,第 6 课把一个蓝图类整个搬进了 .verse 文件。到这里,你已经能写出一个跑得起来的类了——但真实项目从来不止一个类。第二章的最后一课要收的尾,是代码本身怎么分门别类地放:哪些话是写给人看的、哪些定义该装进同一个盒子、别处的代码又怎么找到这个盒子。在蓝图里,这三件事分别由 Comment 框、资产文件夹和资产引用负责;在 Verse 里,它们叫注释、模块和 using

代码有两拨读者:一拨是编译器,另一拨是人——你的队友,以及三个月后回头看代码、已经忘光当时思路的你自己。注释就是专门写给后一拨读者的留言条:编译器读到它会直接跳过,它对程序的行为没有任何影响,却能救下无数「这行到底想干嘛」的深夜。

Verse 提供三种注释写法,各管一种场景:

comment_gallery.verse
# 行注释:# 到行尾都是写给人看的

MaxPlayers:int = 8  # 也可以跟在代码后面补一句

<# 块注释:想写几行写几行,
   直到遇到收尾符号才结束 #>

Total:int = 1 <# 甚至能塞进行中间 #> + 2

<#>
    缩进注释:这一段比上面的符号缩进更深,
    所以整段都是留言,编译器一个字都不看。

(蓝图视角:你在蓝图里留言,是拖一个灰色 Comment 注释框、或右键节点写 Node Comment;Verse 没有框可拖,改成用 #<# #><#> 这几个记号在代码里直接打字。形式换了,作用一样:只给人看,Compile 时被当空气。)

「注释永不执行」这件事,与其背下来不如亲眼看一遍。下面这台设备里埋了一段被块注释包住的旧代码,点「运行下一步」,看看它有没有戏份:

memo_device.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# 这台设备只干一件事:证明注释从不执行
memo_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        Print("步骤 1:开机自检")  # 行尾注释,不影响左边
        <# 这句旧台词已经退役,编译器看都不看:
           Print("我是被注释掉的代码")
        #>
        Sleep(1.0)
        Print("步骤 2:注释全程零出场")
输出日志

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

顺带一提:社区里常说块注释可以互相嵌套,但不同版本的表现有出入,课程先不依赖这个特性,嵌套的确切规则以 Verse API Reference 为准——真要包住含 #> 的内容时,自己先 Build 一次验证。

二、模块:代码世界的分格工具墙

想象一面摆满工具的墙:如果锤子、螺丝、创可贴、薯片全堆在一个大箱子里,找任何东西都要翻半天。真实项目也一样——设备、角色、随机数、UI,成百上千个蓝图和资产如果全塞进同一个大抽屉、名字还不分家,重名、拿错、翻不到,样样都来。Verse 的解法就是模块(module):把相关的定义装进同一个盒子,盒子有名字、有边界、可以整盒搬走复用。

官方对模块的定义相当郑重:「Verse 模块是可再分发、可被依赖的原子代码单元,能在不破坏依赖方的情况下随时间演进。」翻译成人话:模块是发布 API 的最小单位,Epic 可以升级模块内部实现,而你的代码照跑不误。今天在 UEFN 里,你能调用的一切官方能力——每台设备、每个函数——都住在某个模块里;甚至你自己项目 Verse 目录下的每个文件夹,也会自动成为一个同名模块(这是组织多文件项目的关键,拓展页里有完整攻略)。

所以「写 Verse」的第一步永远是:想清楚要用的东西住在哪个盒子里,然后把那个盒子搬过来——搬盒子的动作,就是 using。

三、using:把工具箱搬上工作台

using 的语法只有一种,花括号一个都不能少:

典型的文件开头
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

它的作用:把指定模块(那个工具抽屉)里对外开放的东西一次性引进来,之后你就能直接写短名字 creative_device,而不用每次念一长串完整地址。这一步就像在项目里勾选启用一个插件,或把某个资产文件夹拉进引用范围。不写 using 也不是不能活,把那串完整长地址从头背到尾照样能用,只是没人乐意每行都背。习惯上,所有 using 都堆在 .verse 文件最上面,像开工前先把这一天要用的工具一次摆上台面。

常用路径各管什么,先混个脸熟,忘了随时回来查:

模块路径 管什么 代表成员
/Fortnite.com/Devices creative_device 基类与全部内置设备 creative_devicebutton_device
/Fortnite.com/Characters 游戏角色 fort_character
/Fortnite.com/Game Fortnite 玩法层的通用定义
/Verse.org/Simulation 时间与仿真 Sleep
/Verse.org/Random 随机数 GetRandomIntGetRandomFloat
/Verse.org/Colors 颜色 color
/UnrealEngine.com/Temporary/Diagnostics 调试日志工具 log 日志类
/UnrealEngine.com/Temporary/SpatialMath 空间数学 vector3
/UnrealEngine.com/Temporary/UI 界面

注意到没有?这些路径长得像网址:以 / 开头,顶层是 Fortnite.comVerse.orgUnrealEngine.com 这样的互联网域名。这是有意为之的设计——域名标明了 API 的归属和稳定性承诺:/Verse.org 是语言与通用运行时层面的能力,/Fortnite.com 属于 Fortnite 生态,/UnrealEngine.com 来自引擎侧;而路径里的 Temporary 段是官方在明说「这些 API 是临时安置,未来会搬家」——所以见到它别慌,也别惊讶于某天要改 using。另一个冷知识:Print 本尊住在 /Verse.org/Verse 模块里,这个模块不用显式 using 就能用,所以前面几课从没为 Print 写过导入;模板里那行 Diagnostics 提供的其实是 log 日志类这类调试工具。

动手时间:下面这台「打盹设备」的开头被挖掉了两个词——一个是搬工具箱的关键字,一个是 Sleep 所住的模块名。填回去,点「检查答案」:

(读图翻译:这台设备等于 Event BeginPlay 里串一个 Print String「眯 3 秒……」→ 一个 3 秒的 Delay → 再一个 Print String「醒了!」;开头那几行 using,就是把 Delay 和打印字所在的抽屉先勾进来。)

nap_device.verse
____ { /Fortnite.com/Devices }
using { /Verse.org/____ }
using { /UnrealEngine.com/Temporary/Diagnostics }

nap_device := class(creative_device):

    OnBegin<override>()<suspends>:void =
        Print("眯 3 秒……")
        Sleep(3.0)
        Print("醒了!")

四、自己动手建模块:先混个脸熟

用别人的模块会了,自己也能划一块地盘。最直接的写法,是手写一小段 module 声明,给自己开一个新抽屉:

tools.verse
# 用 module 表达式手动划一块地盘
tools<public> := module:
    Greet<public>():void =
        Print("来自 tools 模块的问候")

(读图翻译:上面这段等于新建了一个叫 tools 的工具抽屉,里面放了一个蓝图函数 Greet,函数体就一个 Print String;两处 <public> 是给抽屉和函数各挂一块「对外可见」的牌子。)

之后在别的文件里写 using { tools }(把 tools 这个抽屉拉开),就能直接喊 Greet();懒得拉抽屉,写全名 tools.Greet() 也能找到它。另一条更常用的路是隐式模块:项目 Verse 目录下的每个文件夹自动成为同名模块,建文件夹就是建模块,一行代码都不用写。

代码上那对 <public> 尖括号,作用就是蓝图里给变量、函数选 Public / Protected / Private 的那个访问权限设置——只不过 Verse 把它写成尖括号挂在名字上。Verse 有四档:public(谁都能用)、internal(默认,仅当前模块内)、protected(类及子类)、private(仅当前类)。这里只需要记一条铁律:想让模块外的代码用上某个成员,模块和成员两个都得标 <public>——漏掉任何一个,对面就会撞上访问权限错误。模块的组织术与嵌套引用,本课拓展页里有完整攻略;可见性说明符的全套规则,留到第 22 课 specifiers 与 attributes 再深挖。现在混个脸熟就够。

五、常见坑:忘了 using 的那一刻

坑一:忘写 using,收获「Unknown identifier」。这是新建空白 .verse 文件时的头号事故:兴冲冲写下 my_device := class(creative_device):,一 Build,编译器甩来一句「Unknown identifier」——它不认识 creative_device。原因很简单:creative_device 不是关键字,只是 /Fortnite.com/Devices 模块里的一个类,工具箱没搬来,里面的工具自然不存在。以后凡是见到 Unknown identifier,第一反应就该是:是不是少了一行 using?

坑二:using 漏了花括号。写成 using /Fortnite.com/Devices 直接是语法错误。花括号是 using 语法的一部分,不是装饰。

坑三:块注释忘了闭合。<# 写了,#> 忘了,后面一大段正经代码全被吞成注释——更阴险的是,报错位置往往在文件很靠后的地方,离真正的案发现场十万八千里。看到莫名其妙的大面积报错,回头检查有没有没闭合的 <#

坑四:随手改文件夹名。文件夹就是模块,重命名或移动文件夹等于改了模块路径,轻则一串 using 失效,重则关卡里已放置的 Verse 设备丢失类引用。改名前先想想有多少地方引用了它。

另外,导入嵌套模块时顺序有讲究——先导入外层再导入子模块,反过来会报错。这个坑连同跨文件夹引用的完整心法,见本课拓展页。

蓝图对照

「代码怎么组织」这件事,你在蓝图里其实一直在做,只是它藏在编辑器 UI 和资产系统里。逐条对齐一遍,差异那一列才是重点。

蓝图里的做法 Verse 里的写法 差异
留言:拖一个灰色 Comment 框圈住几个节点,或右键节点写 Node Comment # 行注释、<# … #> 块注释、<#> 缩进注释 蓝图的 Comment 框是个有位置、有颜色、能拖动的对象;Verse 的注释只是几个字符,不占画布、也不会被谁不小心拖走
用一个节点:在节点面板里搜名字,搜得到就能拉出来用 using { /Fortnite.com/Devices },之后才能直接写 creative_device 蓝图的节点面板默认把能用的东西全摊开给你;Verse 要求你先点名要哪个模块,没点名的东西一律「不存在」
攒一堆可复用函数:建一个 Blueprint Function Library 资产,把函数放进去 建一个模块——写 tools<public> := module:,或者直接建一个文件夹 Function Library 只能装函数;Verse 模块什么都能装:类、函数、常量、枚举、别的模块
查一个节点的来路:悬浮提示或 Details 面板里写着它来自哪个插件/模块 模块路径本身就是来路:/Verse.org/Simulation/UnrealEngine.com/Temporary/SpatialMath 蓝图里「来自哪」是要点开才看得见的元数据;Verse 里它就写在文件第一屏,而且 Temporary 这种字眼会直接告诉你这批 API 将来会搬家
组织项目:靠资产引用 + Content Browser 的文件夹树,重名靠资产路径区分 靠命名空间:模块路径把名字分了家,同名的两个 door 分属两个模块就互不打架 资产引用是二进制里的指针,只能用编辑器的 Reference Viewer 看;模块路径是纯文本,可以全局搜索、可以 diff、可以在 code review 里读出来
依赖声明:没有这一层。一个蓝图用了谁,得靠 Reference Viewer 反查 文件顶部那几行 using 就是这个文件的依赖清单 多写几行的代价,换来的是「打开文件第一眼就知道它依赖什么」——也换来了那个经典错误:忘了 using,收获 Unknown identifier

这些差异有一个共同的根:蓝图把「谁能看见谁」交给编辑器,Verse 把它交给你写在文件里的文字。节点面板替你把可用的节点筛好了,资产引用在你拖拽的瞬间自动建立,连访问权限都藏在 Details 面板的一个下拉框里——蓝图的组织结构大半是 UI 的副产品。Verse 没有 UI 可依赖,于是把同样的信息全部提到台面上:用什么就 using 什么,想开放就标 <public>,模块边界就是文件夹边界。

写出来的东西看着比蓝图啰嗦,回报是它变成了可搜索、可比对的文本。「这个文件依赖哪些模块」在蓝图里要开 Reference Viewer 慢慢点,在 Verse 里就是文件开头三行;「上周谁改了模块的可见性」在蓝图里几乎查不出来,在 Verse 里是一行 diff。这也是为什么第 4 课那张名词表里,很多蓝图概念在 Verse 一侧的对应物不是某个功能,而是「一段你自己写的文字」。

六、关卡挑战

三道小关卡,验收这一课的战利品。答错零惩罚,可以一直重试。

新建了一个空白 .verse 文件,顶部什么都没写,直接来一句 my_device := class(creative_device): 然后 Build,结果会怎样?

想把一大段旧代码整块注释掉,应该用哪种写法?

代码里写了 Sleep(2.0),Build 时报「Unknown identifier」。最可能缺了哪行 using?

拓展阅读

拓展 · EXTRA

digest 文件:比官网更新鲜的 API 词典

每次构建都会生成的 .digest.verse 文件,是与你本机版本严格同步的全量 API 清单——老手查 API 的第一站。

进入拓展 →

拔高 · EXTRA

从 .vmodule 到「目录即模块」

UEFN 23.20 的模块体系变革:为什么今天建个文件夹就是建个模块,以及默认 internal 背后的设计动机。

进入拓展 →

技巧 · EXTRA

跨文件夹引用自己模块的正确姿势

同目录、子文件夹、嵌套模块三种场景的 using 写法速查,外加「先外层后子模块」的导入顺序铁律。

进入拓展 →