Verse Wiki — 写给蓝图作者的 Verse 手册
拔高 · EXTRA

「把蓝图拖进关卡」在 Verse 里发生了什么

正课最后一步是「把类拖进关卡」。这个动作在蓝图里你做过几百次,但可能从没想过它到底做了什么——它把一张图纸变成了场上一个真实存在的东西。这一页把「类」和「实例」这对概念彻底分开,再回答一个正课里埋下的问题:为什么 Verse 类的字段可以先不给默认值?

一、类是图纸,实例是场上的东西

你在 Content Browser 里看到的 BP_MyDoor 是一个——一张图纸,描述「一扇门应该有哪些变量、开门时该做什么」。它自己不在世界里,它只是说明书。

你把它拖进 Viewport,World Outliner 里多出一行 BP_MyDoor_C_0,这才是一个实例:它有坐标、有自己的一份变量值、会被 Event BeginPlay 唤醒。再拖一个,就有第二个实例 BP_MyDoor_C_1,和第一个各活各的。

这套关系在 Verse 里一字不差:

顺手澄清两个新手常混的操作,两边完全一致:改(蓝图资产 / Verse 代码)会影响所有实例;改某个实例的 Details 面板,只影响那一个。

二、实例化:一个动作,两条路

「从图纸造出一个真东西」这个动作叫实例化(instantiate)。蓝图里它有两条路,Verse 也一样。

路一:在编辑器里放置

蓝图:把资产拖进 Viewport。Verse:把编译产物拖进关卡。这条路上你不写任何代码——是编辑器替你造了这个实例。这也是为什么正课强调「没被拖进关卡的类,一行代码都不会执行」:图纸躺在抽屉里,没人照着它造东西。

路二:在代码里造

蓝图:接一个 Spawn Actor from Class 节点,运行时凭空造一个出来。Verse 用一对大括号做同一件事:

door_config.verse
# 类:一张图纸。不继承 creative_device,所以它拖不进关卡,
# 只能在代码里被造出来
door_config := class:
    DoorName:string = "北门"
    OpenSeconds:float = 1.5

# 实例化:大括号 = 「照这张图纸造一个」
# 括号里可以顺手改掉某些字段,没提到的就用默认值
SouthDoor := door_config{DoorName := "南门"}

door_config{...} 就是 Spawn 节点。大括号里那句 DoorName := "南门",对应的是 Spawn Actor 节点上那些「Expose on Spawn」的输入引脚——造这一个的时候,顺手给它一个和默认值不同的设定。没写进大括号的 OpenSeconds,就还是图纸上的 1.5。

注意上面这个类没有父类(class: 后面直接跟内容)。这是故意的:它说明 Verse 里的类不必都是设备。creative_device 那条继承线只是为了满足「能被拖进 Fortnite 关卡」这个运行环境要求;不需要被放置的纯数据类、工具类,写成普通类就好,由代码来造。

三、为什么类的字段可以不给默认值

第 8 课会讲一条规矩:函数里、模块里声明的常量和变量,必须当场给初始值,不存在「先挂块空牌子」。但类的字段是个例外——它可以只写类型、不给值:

door_config.verse
door_config := class:
    # 没有 = 值:这块牌子出厂时是空的
    DoorName:string

    # 有默认值:不填就用 1.5
    OpenSeconds:float = 1.5

# 造实例时必须把空着的那块补上,否则编译不过
NorthDoor := door_config{DoorName := "北门"}

为什么允许?因为类字段和局部常量的生效时机不一样。局部常量在执行到那一行时就得有值,不然下一行没法用;而类字段属于还没被造出来的东西——图纸上留一个空格,不代表这个空格永远是空的,它只是把「谁来填」推迟到了实例化那一刻。

于是规则变成一句话:字段没有默认值,就由创建这个实例的人负责填。而「创建这个实例的人」正好对应上一节的两条路:

第二条正是蓝图作者最熟的场景。蓝图里你在变量上勾 Instance Editable(变量旁边那个小眼睛),它就会出现在实例的 Details 面板里,每个摆在关卡里的实例都能填不同的值。Verse 里对应的是 @editable:

my_door.verse
using { /Fortnite.com/Devices }

my_door := class(creative_device):

    # @editable:这个字段会出现在实例的 Details 面板里
    @editable
    DoorName:string = "北门"

    # 没有 @editable:关卡里看不到,只有代码能改
    OpenSeconds:float = 1.5

加了 @editable,你把 my_door 拖两个进关卡,就能在 Details 面板里把一个叫「北门」、另一个叫「南门」——同一张图纸,两个不同配置的实例。这就是蓝图里那个小眼睛。

一条实务提醒:在今天的 UEFN 里,@editable 字段通常需要写一个默认值,编辑器才有东西可显示——设备引用类型常见的写法是拿一个空的实例(比如 button_device{})当占位。所以「不给默认值」这条自由,主要用在代码实例化那条路上。

四、蓝图对照

蓝图里的做法 Verse 里的写法 差异
Content Browser 里的 BP_MyDoor 资产(类) my_door := class(creative_device): 同为图纸。Verse 的图纸是文本,一个文件里可以放好几张
拖进 Viewport,Outliner 多一行(实例) 编译产物拖进关卡的那一个 动作完全一致:都由编辑器负责实例化,你不写代码
Spawn Actor from Class 节点(运行时造实例) door_config{DoorName := "南门"} Verse 用大括号,而且可以在括号里直接给任意字段赋值,不必事先把它标成 Expose on Spawn
变量勾 Instance Editable(小眼睛)→ 出现在 Details 面板 字段前加 @editable 作用一致。Verse 用一行标注代替一次勾选
Class Defaults 里改默认值 → 影响所有实例 改类里字段的默认值 一致:改图纸影响全体,改实例只影响一个

一个 Verse 类里写了 DoorName:string,没给默认值。这会怎样?

五、来源

本文整理自 Epic 官方文档: