技巧 · EXTRA
OnBegin 先睡一帧:Sleep(0.0) 的社区偏方
为什么老手都在 OnBegin 第一行写 Sleep(0.0)?一行代码,躲开第一帧的静默失败。
进入拓展 →上一课你认识了 Verse 这门语言,这一课把代码变成实物:亲手定义一个 creative_device,逐 token 读懂 OnBegin 那行固定写法,再走一遍 Build → 拖放 → 启动会话的完整流程,让你的第一块逻辑积木真正在关卡里跑起来。
上一课你认识了 Verse 这门语言,这一课把代码变成实物:亲手定义一个 creative_device,把 OnBegin 那行固定写法拆成一个个零件读懂(像拆开一个蓝图节点、看清它每一个引脚),再走一遍 Build → 拖放 → 启动会话的完整流程,让你的第一块逻辑积木真正在关卡里跑起来。
玩过 Fortnite 创意模式的你一定摆弄过各种设备:按钮、屏障、计分板、跳板……它们像乐高积木,拖进关卡、调调参数,机制就成型了。但官方积木总有不够用的一天——这时候就轮到 creative_device 出场了。
creative_device 是 Verse 自制设备的基类,住在 /Fortnite.com/Devices 模块里,是所有「自定义设备」的祖师爷。只要你的类继承了它,编译之后你的代码就会变成一块崭新的积木:出现在 Content Browser 里,可以像官方设备一样拖进关卡、摆在地上。换句话说:
creative_device 是 Verse 自制设备的父蓝图,住在 /Fortnite.com/Devices 这个抽屉(模块)里,是所有「自定义设备」的祖师爷。你新建的设备只要认它当爹(就像在蓝图里基于某个父类新建一个 Child Blueprint Class),Compile 之后你的代码就会变成一块崭新的积木:出现在 Content Browser 里,可以像官方设备一样拖进关卡、摆在地上。换句话说:
creative_device 的类 = 你自己开模生产的积木,逻辑随便写。creative_device 当父蓝图的设备 = 你自己开模生产的积木,逻辑随便写。有两条铁律现在就要记住。其一:凡是想拖进关卡的 Verse 类,都必须继承 creative_device——不继承它,代码写得再漂亮也只是一张没法上桌的图纸。其二:只有真正被放进关卡的设备实例才会执行,而且你拖几个进去,就有几份逻辑各自开跑——同一块积木,可以在岛上不同角落各干各的活。
有两条铁律现在就要记住。其一:凡是想拖进关卡的 Verse 设备,都必须认 creative_device 当父蓝图——不认这个爹,代码写得再漂亮也只是一张没法上桌的图纸。其二:只有真正被放进关卡的设备实例才会执行,而且你拖几个进去,就有几份逻辑各自开跑——同一块积木,可以在岛上不同角落各干各的活。
定义一个设备只需要一行声明。先看一个最小可用的完整示例:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
# 一个最小可用的自定义设备
my_device := class(creative_device):
OnBegin<override>()<suspends>:void =
Print("设备已上线!")
读图翻译:这段代码就是一张蓝图。开头两行 using 相当于「先把 Devices、Diagnostics 两个抽屉拉开」,好让下面用得到里面的零件;my_device := class(creative_device) 是「新建一块以 creative_device 为父的蓝图」;OnBegin 那行就是这张图里的 Event BeginPlay 节点;它下面缩进的 Print 则是接了一个 Print String 节点,在屏幕左上角打出「设备已上线!」。
把声明那一行拆开看,一共四个零件:
my_device —— 你给设备起的名字。类名用 lower_snake_case(全小写 + 下划线),这是官方 Verse Code Style Guide 定下的命名礼仪,creative_device 本身就是范例;:= —— 读作「定义为」。左边是名字,右边是这个名字到底是什么。注意它和单独的 = 不是一回事——在 Verse 里 = 是比较,定义新东西认准 :=;class(creative_device) —— 宣布「我是一个类,而且继承了 creative_device 的全部家产」:能被拖进关卡、能在游戏体验开始时自动醒来;class(creative_device) —— 宣布「我是一块蓝图,而且认 creative_device 当父、继承了它的全部家产」(就像基于父类建一个 Child Blueprint Class):能被拖进关卡、能在游戏体验开始时自动醒来;: + 下一行缩进 4 空格 —— Verse 没有大括号,缩进就是代码块。冒号说「里面的内容在下面」,4 个空格说「我就是里面的内容」。: + 下一行缩进 4 空格 —— Verse 没有大括号,缩进就是分组。在蓝图里,一个节点接在哪根执行线、归哪个事件管,是靠连线表达的;Verse 没有连线,就用缩进代替——冒号说「里面的内容在下面」,往里缩 4 个空格的那些行,就都算「接在这行后面的那串节点」。一句话总结:名字 := class(祖师爷):,然后缩进写本领。就这么简单。
类体里那行 OnBegin<override>()<suspends>:void = 是很多新人第一眼的「劝退现场」——一行里挤了六个零件。别慌,我们像拆武器配件一样,一个 token 一个 token 过:
设备蓝图里面那行 OnBegin<override>()<suspends>:void = 是很多新人第一眼的「劝退现场」——一行里挤了六个零件。别慌,我们像拆武器配件一样,一个零件一个零件过:
| token | 读作 | 它在干什么 | |
|---|---|---|---|
OnBegin |
「开场时」 | 函数名。它是 creative_device 家传的生命周期函数,游戏体验开始时由引擎自动调用,不用你喊它。函数名用 PascalCase(首字母大写),和类名的小写蛇形正好相反 | 这就是设备的 Event BeginPlay——游戏体验一开始,引擎自动帮你触发它,不用你去 Call。名字用 PascalCase(首字母大写,像 OnBegin),和设备名那种全小写加下划线(my_device)正好相反 |
<override> |
「重写徽章」 | 说明符:OnBegin 在基类里已有定义,你要覆写这门家传绝学,就必须亮出这块徽章。漏写 = 编译错误 | 这块徽章说明「我要重写父蓝图里已有的 OnBegin」——就像在蓝图的 Override 下拉菜单里挑一个父类函数来改写它。漏写这块徽章,Compile 时就会亮红报错 |
() |
「不收参数」 | 参数列表。OnBegin 什么参数都不收,但空括号一个都不能省 | |
<suspends> |
「打坐徽章」 | 说明符:声明这是一个可挂起的异步函数,能中途暂停(比如 Sleep 等待)再醒来。基类的 OnBegin 签名带它,你的重写也必须带,少一个都算签名不匹配 | 这块徽章说明这是个能中途等待的函数——里面可以放像 Delay 那样带小钟表的节点(比如 Sleep 等几秒)再继续。父蓝图的 OnBegin 带着它,你重写时也必须带,少一块就跟父蓝图对不上号 |
:void |
「不交作业」 | 返回类型。void 表示这个函数不返回有意义的值——它只管干活,不用上交结果 | 这是「交回什么」的说明。void 表示这个函数不往外交任何值——它的 Return 节点没有输出引脚,只管干活,不用上交结果 |
= |
「定义为」 | 签名到此为止,函数体从下一行缩进 4 空格处开始 | 这行的定义部分到此结束;真正要跑的那串节点,从下一行往里缩 4 空格处开始接上 |
现阶段的记法很粗暴:这一整行是固定写法,原样背下来。它的官方语义是「Override 此函数,在游戏体验开始时加入自定义逻辑」。而正因为它自带 <suspends>,函数体里可以直接调用 Sleep(秒数) 这类异步 API——这是下一节的主角。而正因为它自带 <suspends>(等待徽章),函数体里可以直接放 Sleep(秒数) 这类带小钟表、会等待的节点(就像蓝图的 Delay)——这是下一节的主角。顺带一提,OnBegin 还有个孪生兄弟 OnEnd<override>():void =,在游戏体验结束时运行,注意它不带 suspends;它的故事我们放在本课的拔高拓展里讲。
把这一课的零件拼起来,造一台「迎宾设备」:游戏一开始先喊一句欢迎,然后原地打坐 3 秒,再补一句提示。Sleep 来自 /Verse.org/Simulation 模块,所以文件顶部多了一行 using。Sleep 住在 /Verse.org/Simulation 这个抽屉里,所以文件顶部多拉开了一个抽屉(顶上那行 using)。点「运行下一步」,亲眼看代码怎么一行行执行——尤其注意 Sleep(3.0) 那一步,输出日志会一动不动,那就是「等待」长的样子。
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# 一个会打招呼的迎宾设备
welcome_device := class(creative_device):
OnBegin<override>()<suspends>:void =
Print("欢迎来到我的岛屿,冒险者!")
Sleep(3.0)
Print("提示:出生点旁边有补给箱,拿了再出发。")
点「运行下一步」,看代码怎么一行行执行。
小实验:重置后再跑一遍,想象没有 Sleep 的版本——两句话会挤在同一瞬间蹦出来,玩家可能一句都没看清。节奏感,也是设计的一部分。
纸上谈兵到此为止,下面是把 welcome_device 真正放进游戏的完整流程。四步走完,你就拥有了人生第一块自制积木:
打开 Verse Explorer 面板,右键你的项目名,选「Add new Verse file to project」,模板选 Verse Device。双击生成的 .verse 文件,它会在 Visual Studio Code 里打开——把上面的代码手抄进去,肌肉记忆很宝贵。
回到 UEFN,点工具栏 Verse 菜单里的「Build Verse Code」,或直接按 Ctrl+Shift+B,项目里所有 .verse 文件一起编译。成功时 Verse 按钮上出现绿色对勾;失败则是红色停止图标,点错误弹窗里的「See Errors」会打开 Message Log 列出全部错误——修完所有错误之前,代码没法进游玩测试。
编译成功后,在 Content Browser 的项目内容目录里就能找到 welcome_device——恭喜,你的积木下线了!把它拖到岛上任意位置,它是逻辑积木,不挑风水。记住:没被拖进关卡的设备,一行代码都不会执行。
点「Launch Session」启动游戏会话。Print 的消息会同时出现在三个地方:游戏画面上的调试文字、游戏内日志,以及 UEFN 的 Output Log(输出日志)面板。你会看到欢迎语先蹦出来,3 秒后提示语跟上——和步进器里演的一模一样。
如果第 4 步的日志和你预想的一致,停下来庆祝十秒钟:你已经完成了「写代码 → 编译 → 部署 → 验证」的完整闭环,这可是不少人卡了好几天的地方。
本课的 BOSS 战由一道填空 + 三道选择题组成。答对拿星星,答错随便重试,没有惩罚机制。
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
my_first_device := ____(____):
OnBegin<____>()<____>:void =
Print("我的第一个设备上线了!")
代码编译通过(绿色对勾都亮了),启动会话后却什么都没发生。最可能的原因是?
把 OnBegin 写成了 OnBegin<override>():void =(漏掉 suspends 徽章),会发生什么?
在 UEFN 里按 Ctrl+Shift+B 的作用是什么?
技巧 · EXTRA
为什么老手都在 OnBegin 第一行写 Sleep(0.0)?一行代码,躲开第一帧的静默失败。
进入拓展 →拓展 · EXTRA
不重启会话就换上新逻辑——Verse 热更新与增量烘焙,把你从「改一行重启一次」里解救出来。
进入拓展 →拔高 · EXTRA
设备从醒来到谢幕的完整一生,以及官方为什么警告「别在 OnEnd 里 spawn」。设备从醒来到谢幕的完整一生,以及官方为什么警告「别在 OnEnd 里 spawn(临时岔出一条新执行线)」。
进入拓展 →