拓展 · EXTRA
digest 文件:比官网更新鲜的 API 词典
每次构建都会生成的 .digest.verse 文件,是与你本机版本严格同步的全量 API 清单——老手查 API 的第一站。
进入拓展 →前两课的模板里,那三行 using 你抄了两遍还没问过它们是干嘛的。这一课补上:先学会用 # 和 <# #> 给人类留言,再搞清 using 每次都在搬哪个工具箱,顺便看懂 Verse 那套域名式模块路径的设计。
代码有两拨读者:一拨是编译器,另一拨是人——你的队友,以及三个月后回头看代码、已经忘光当时思路的你自己。注释就是专门写给后一拨读者的留言条:编译器读到它会直接跳过,它对程序的行为没有任何影响,却能救下无数「这行到底想干嘛」的深夜。
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:注释全程零出场")
点「运行下一步」,看代码怎么一行行执行。
顺带一提:社区里常说块注释可以互相嵌套,但不同版本的表现有出入,课程先不依赖这个特性。# TODO: 以 Verse API Reference 为准——真要包住含 #> 的内容时,自己先 Build 一次验证。
想象一面摆满工具的墙:如果锤子、螺丝、创可贴、薯片全堆在一个大箱子里,找任何东西都要翻半天。真实项目的代码也一样——设备、角色、随机数、UI,几百上千个定义如果全挤在一个命名空间里,重名、误用、找不到,全都会来。真实项目也一样——设备、角色、随机数、UI,成百上千个蓝图和资产如果全塞进同一个大抽屉、名字还不分家,重名、拿错、翻不到,样样都来。Verse 的解法就是模块(module):把相关的定义装进同一个盒子,盒子有名字、有边界、可以整盒搬走复用。
官方对模块的定义相当郑重:「Verse 模块是可再分发、可被依赖的原子代码单元,能在不破坏依赖方的情况下随时间演进。」翻译成人话:模块是发布 API 的最小单位,Epic 可以升级模块内部实现,而你的代码照跑不误。你在 UEFN 里用到的一切官方能力——每台设备、每个函数——都住在某个模块里;甚至你自己项目 Verse 目录下的每个文件夹,也会自动成为一个同名模块(这是组织多文件项目的关键,拓展页里有完整攻略)。
所以「写 Verse」的第一步永远是:想清楚要用的东西住在哪个盒子里,然后把那个盒子搬过来——搬盒子的动作,就是 using。
using 的语法只有一种,花括号一个都不能少:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
它的作用:把指定模块里的 public 定义引入当前文件,之后就能直呼其名——写 creative_device 而不是每次都写全路径。不 using 也不是不能活,用完整限定名同样可以访问,只是没人愿意每行都背地址。习惯上,所有 using 都写在 .verse 文件顶部,像游戏开场先亮装备栏。
它的作用:把指定模块(那个工具抽屉)里对外开放的东西一次性引进来,之后你就能直接写短名字 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 声明,给自己开一个新抽屉:
# 用 module 表达式手动划一块地盘
tools<public> := module:
Greet<public>():void =
Print("来自 tools 模块的问候")
(读图翻译:上面这段等于新建了一个叫 tools 的工具抽屉,里面放了一个蓝图函数 Greet,函数体就一个 Print String;两处 <public> 是给抽屉和函数各挂一块「对外可见」的牌子。)
之后在别的文件里 using { tools },就能直接调用 Greet();不导入的话,写 tools.Greet() 加限定名访问也行。之后在别的文件里写 using { tools }(把 tools 这个抽屉拉开),就能直接喊 Greet();懒得拉抽屉,写全名 tools.Greet() 也能找到它。另一条更常用的路是隐式模块:项目 Verse 目录下的每个文件夹自动成为同名模块,建文件夹就是建模块,一行代码都不用写。
代码上那对 <public> 尖括号是可见性说明符。代码上那对 <public> 尖括号,作用就是蓝图里给变量、函数选 Public / Protected / Private 的那个访问权限设置——只不过 Verse 把它写成尖括号挂在名字上。Verse 有四档:public(谁都能用)、internal(默认,仅当前模块内)、protected(类及子类)、private(仅当前类)。这里只需要记一条铁律:想让模块外的代码用上某个成员,模块和成员两个都得标 <public>——漏掉任何一个,对面就会撞上访问权限错误。模块的组织术、嵌套与可见性的完整玩法,我们放到第 16 课之后的进阶章节再深挖,现在混个脸熟就够。
坑一:忘写 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 设备丢失类引用。改名前先想想有多少地方引用了它。
另外,导入嵌套模块时顺序有讲究——先导入外层再导入子模块,反过来会报错。这个坑连同跨文件夹引用的完整心法,见本课拓展页。
三道小关卡,验收这一课的战利品。答错零惩罚,可以一直重试。
新建了一个空白 .verse 文件,顶部什么都没写,直接来一句 my_device := class(creative_device): 然后 Build,结果会怎样?
想把一大段旧代码整块注释掉,应该用哪种写法?
代码里写了 Sleep(2.0),Build 时报「Unknown identifier」。最可能缺了哪行 using?