拓展 · EXTRA
digest 文件:比官网更新鲜的 API 词典
每次构建都会生成的 .digest.verse 文件,是与你本机版本严格同步的全量 API 清单——老手查 API 的第一站。
进入拓展 →第二章前三课对齐了名词、结构和类,这一课收尾:代码本身怎么分门别类地放。注释是写给人看的留言条,模块是分了格的工具墙,using 是把工具箱搬上工作台——蓝图靠资产与文件夹维持的那套秩序,在文本世界里就是这三样。
第 4 课给了你一张名词对照表,第 5 课把节点图掰成了缩进块,第 6 课把一个蓝图类整个搬进了 .verse 文件。到这里,你已经能写出一个跑得起来的类了——但真实项目从来不止一个类。第二章的最后一课要收的尾,是代码本身怎么分门别类地放:哪些话是写给人看的、哪些定义该装进同一个盒子、别处的代码又怎么找到这个盒子。在蓝图里,这三件事分别由 Comment 框、资产文件夹和资产引用负责;在 Verse 里,它们叫注释、模块和 using。
代码有两拨读者:一拨是编译器,另一拨是人——你的队友,以及三个月后回头看代码、已经忘光当时思路的你自己。注释就是专门写给后一拨读者的留言条:编译器读到它会直接跳过,它对程序的行为没有任何影响,却能救下无数「这行到底想干嘛」的深夜。
Verse 提供三种注释写法,各管一种场景:
#——从 # 到行尾全是留言。可以独占一行,也可以跟在代码后面补一句。<# … #>——成对出现,中间想写几行写几行;甚至能塞进一行代码的中间当行内说明。<#>——以 <#> 开头的行往下,所有缩进更深的行都算注释,缩进一退回来注释就结束——靠缩进划定留言的地盘,很有 Verse 的风格。# 行注释:# 到行尾都是写给人看的
MaxPlayers:int = 8 # 也可以跟在代码后面补一句
<# 块注释:想写几行写几行,
直到遇到收尾符号才结束 #>
Total:int = 1 <# 甚至能塞进行中间 #> + 2
<#>
缩进注释:这一段比上面的符号缩进更深,
所以整段都是留言,编译器一个字都不看。
(蓝图视角:你在蓝图里留言,是拖一个灰色 Comment 注释框、或右键节点写 Node Comment;Verse 没有框可拖,改成用 #、<# #>、<#> 这几个记号在代码里直接打字。形式换了,作用一样:只给人看,Compile 时被当空气。)
「注释永不执行」这件事,与其背下来不如亲眼看一遍。下面这台设备里埋了一段被块注释包住的旧代码,点「运行下一步」,看看它有没有戏份:
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 { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
它的作用:把指定模块(那个工具抽屉)里对外开放的东西一次性引进来,之后你就能直接写短名字 creative_device,而不用每次念一长串完整地址。这一步就像在项目里勾选启用一个插件,或把某个资产文件夹拉进引用范围。不写 using 也不是不能活,把那串完整长地址从头背到尾照样能用,只是没人乐意每行都背。习惯上,所有 using 都堆在 .verse 文件最上面,像开工前先把这一天要用的工具一次摆上台面。
常用路径各管什么,先混个脸熟,忘了随时回来查:
| 模块路径 | 管什么 | 代表成员 |
|---|---|---|
/Fortnite.com/Devices |
creative_device 基类与全部内置设备 | creative_device、button_device |
/Fortnite.com/Characters |
游戏角色 | fort_character |
/Fortnite.com/Game |
Fortnite 玩法层的通用定义 | — |
/Verse.org/Simulation |
时间与仿真 | Sleep |
/Verse.org/Random |
随机数 | GetRandomInt、GetRandomFloat |
/Verse.org/Colors |
颜色 | color |
/UnrealEngine.com/Temporary/Diagnostics |
调试日志工具 | log 日志类 |
/UnrealEngine.com/Temporary/SpatialMath |
空间数学 | vector3 |
/UnrealEngine.com/Temporary/UI |
界面 | — |
注意到没有?这些路径长得像网址:以 / 开头,顶层是 Fortnite.com、Verse.org、UnrealEngine.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 和打印字所在的抽屉先勾进来。)
____ { /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 声明,给自己开一个新抽屉:
# 用 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,收获「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?